(добавление ответа, потому что примеров с универсальными символами Generics недостаточно)
// Source
List<Integer> intList = Arrays.asList(1,2,3);
List<Double> doubleList = Arrays.asList(2.78,3.14);
List<Number> numList = Arrays.asList(1,2,2.78,3.14,5);
// Destination
List<Integer> intList2 = new ArrayList<>();
List<Double> doublesList2 = new ArrayList<>();
List<Number> numList2 = new ArrayList<>();
// Works
copyElements1(intList,intList2); // from int to int
copyElements1(doubleList,doublesList2); // from double to double
static <T> void copyElements1(Collection<T> src, Collection<T> dest) {
for(T n : src){
dest.add(n);
}
}
// Let's try to copy intList to its supertype
copyElements1(intList,numList2); // error, method signature just says "T"
// and here the compiler is given
// two types: Integer and Number,
// so which one shall it be?
// PECS to the rescue!
copyElements2(intList,numList2); // possible
// copy Integer (? extends T) to its supertype (Number is super of Integer)
private static <T> void copyElements2(Collection<? extends T> src,
Collection<? super T> dest) {
for(T n : src){
dest.add(n);
}
}
Методы класса для того, когда у Вас должны быть методы, которые не характерны ни для какого конкретного экземпляра, но все еще включают класс в некотором роде. Самая интересная вещь о них состоит в том, что они могут быть переопределены подклассами, что-то, что это просто не возможно в статических методах Java или функциях уровня модуля Python.
, Если у Вас есть класс MyClass
, и функция уровня модуля, которая воздействует на MyClass (фабрика, тупик внедрения зависимости, и т.д.), делает его classmethod
. Тогда это будет доступно подклассам.
Альтернативные конструкторы являются классическим примером.
Методы фабрики (альтернативные конструкторы) являются действительно классическим примером методов класса.
В основном, методы класса подходят каждый раз, когда требуется иметь метод, который естественно вписывается в пространство имен класса, но не связан с конкретным экземпляром класса.
Как пример, в превосходном модуль unipath :
Path.cwd()
Path("/tmp/my_temp_dir")
. Это - метод класса. .chdir()
, Поскольку текущий каталог является широким процессом, cwd
, метод не имеет никакого конкретного экземпляра, с которым это должно быть связано. Однако изменяясь cwd
к каталогу, учитывая Path
экземпляр должен действительно быть методом экземпляра.
Hmmm... как Path.cwd()
действительно возвращается Path
экземпляр, я предполагаю, что это могло считаться методом фабрики...
Честно? Я никогда не находил использование для staticmethod или classmethod. Я должен все же видеть операцию, которая не может быть сделана с помощью глобальной функции или метода экземпляра.
Это отличалось бы, если используемые частные и защищенные участники Python больше как Java делают. В Java мне нужен статический метод быть в состоянии получить доступ к членам парламента, не занимающим официального поста экземпляра, чтобы сделать материал. В Python это редко необходимо.
Обычно, я вижу, что люди используют staticmethods и classmethods, когда все, что они действительно должны сделать, использовать пространства имен уровня модуля Python лучше.
@classmethod
может быть полезно для того, чтобы легко инстанцировать объектов того класса от внешних ресурсов. Рассмотрите следующее:
import settings
class SomeClass:
@classmethod
def from_settings(cls):
return cls(settings=settings)
def __init__(self, settings=None):
if settings is not None:
self.x = settings['x']
self.y = settings['y']
Затем в другом файле:
from some_package import SomeClass
inst = SomeClass.from_settings()
Доступ inst.x даст то же значение как настройки ['x'].
Раньше я работал с PHP и недавно я спросил себя, что происходит с этим методом? Руководство по Python очень техническое и очень короткое в словах, так что оно не поможет понять эту функцию. Я гуглил, гуглил и нашел ответ -> http://code.anjanesh.net/2007/12/python-classmethods.html.
Если вам лень нажать на него. Мое объяснение короче и ниже. :)
В PHP (возможно, не все из вас знают PHP, но этот язык настолько прост, что все должны понимать, о чем я говорю) у нас есть статические переменные, как здесь:
class A
{
static protected $inner_var = null;
static public function echoInnerVar()
{
echo self::$inner_var."\n";
}
static public function setInnerVar($v)
{
self::$inner_var = $v;
}
}
class B extends A
{
}
A::setInnerVar(10);
B::setInnerVar(20);
A::echoInnerVar();
B::echoInnerVar();
Вывод будет в обоих случаях 20.
Однако в python мы можем добавить декоратор @classmethod и таким образом можно получить на выходе 10 и 20 соответственно. Example:
class A(object):
inner_var = 0
@classmethod
def setInnerVar(cls, value):
cls.inner_var = value
@classmethod
def echoInnerVar(cls):
print cls.inner_var
class B(A):
pass
A.setInnerVar(10)
B.setInnerVar(20)
A.echoInnerVar()
B.echoInnerVar()
Smart, ain't?
Недавно мне понадобился очень легкий класс протоколирования, который бы выводил различное количество информации в зависимости от уровня протоколирования, который может быть установлен программно. Но я не хотел инстанцировать класс каждый раз, когда мне нужно вывести отладочное сообщение, ошибку или предупреждение. Но я также хотел инкапсулировать функционирование этого средства протоколирования и сделать его многократно используемым без объявления каких-либо глобальных файлов.
Поэтому я использовал переменные класса и декоратор @classmethod
для достижения этой цели.
С моим простым классом Logging я мог сделать следующее:
Logger._level = Logger.DEBUG
Затем, в моем коде, если я хотел выплюнуть кучу отладочной информации, я просто должен был написать
Logger.debug( "this is some annoying message I only want to see while debugging" )
Ошибки можно было выводить с помощью
Logger.error( "Wow, something really awful happened." )
В "производственной" среде я могу указать
Logger._level = Logger.ERROR
и теперь будет выводиться только сообщение об ошибке. Отладочное сообщение выводиться не будет.
Вот мой класс:
class Logger :
''' Handles logging of debugging and error messages. '''
DEBUG = 5
INFO = 4
WARN = 3
ERROR = 2
FATAL = 1
_level = DEBUG
def __init__( self ) :
Logger._level = Logger.DEBUG
@classmethod
def isLevel( cls, level ) :
return cls._level >= level
@classmethod
def debug( cls, message ) :
if cls.isLevel( Logger.DEBUG ) :
print "DEBUG: " + message
@classmethod
def info( cls, message ) :
if cls.isLevel( Logger.INFO ) :
print "INFO : " + message
@classmethod
def warn( cls, message ) :
if cls.isLevel( Logger.WARN ) :
print "WARN : " + message
@classmethod
def error( cls, message ) :
if cls.isLevel( Logger.ERROR ) :
print "ERROR: " + message
@classmethod
def fatal( cls, message ) :
if cls.isLevel( Logger.FATAL ) :
print "FATAL: " + message
И немного кода, который его тестирует:
def logAll() :
Logger.debug( "This is a Debug message." )
Logger.info ( "This is a Info message." )
Logger.warn ( "This is a Warn message." )
Logger.error( "This is a Error message." )
Logger.fatal( "This is a Fatal message." )
if __name__ == '__main__' :
print "Should see all DEBUG and higher"
Logger._level = Logger.DEBUG
logAll()
print "Should see all ERROR and higher"
Logger._level = Logger.ERROR
logAll()
Методы класса предоставляют «семантический сахар» (не знаю, широко ли используется этот термин) или «семантическое удобство».
Пример: у вас есть набор классов, представляющих объекты. Возможно, вам понадобится метод класса all ()
или find ()
для записи User.all ()
или User.find (firstname = «Гвидо»)
. Конечно, это можно сделать с помощью функций уровня модуля ...
Подумайте об этом так: обычные методы полезны, чтобы скрыть детали диспетчеризации: вы можете набрать myobj.foo()
, не беспокоясь о том, реализован ли метод foo()
классом объекта myobj
или одним из его родительских классов. Методы класса в точности аналогичны этому, но с объектом класса вместо него: они позволяют вам вызывать MyClass.foo()
, не беспокоясь о том, реализован ли foo()
специально в MyClass
, потому что ему нужна своя специализированная версия, или же он позволяет своему родительскому классу обработать вызов.
Методы класса необходимы, когда вы выполняете установку или вычисления, которые предшествуют созданию реального экземпляра, потому что пока экземпляр не существует, вы, очевидно, не можете использовать его в качестве точки диспетчеризации для вызовов ваших методов. Хороший пример можно увидеть в исходном коде SQLAlchemy; посмотрите на метод класса dbapi()
по следующей ссылке:
Вы можете увидеть, что метод dbapi()
, который бэкенд базы данных использует для импорта необходимой ему библиотеки базы данных конкретного производителя по требованию, является методом класса, потому что он должен выполняться до того, как начнут создаваться экземпляры конкретного соединения с базой данных - но он не может быть простой функцией или статической функцией, потому что они хотят, чтобы он мог вызывать другие, вспомогательные методы, которые могут быть написаны более конкретно в подклассах, чем в их родительском классе. А если вы диспетчеризируете функцию или статический класс, то вы "забываете" и теряете знание о том, какой класс выполняет инициализацию.