Значение @classmethod и @staticmethod для начинающих?

Может ли кто-нибудь объяснить мне значение @classmethod и @staticmethod в python? Мне нужно знать разницу и смысл.

Насколько я понимаю, @classmethod сообщает классу, что это метод, который должен быть унаследован в подклассы, или... что-то. Однако в чем смысл этого? Почему бы просто не определить метод класса без добавления @classmethod или @staticmethod или любых @ определений?

tl; dr:, когда следует использовать их, почему я должен их использовать и как их использовать?

Я довольно продвинутый с С++, поэтому использование более продвинутых концепций программирования не должно быть проблемой. Не стесняйтесь давать мне соответствующий пример С++, если это возможно.

Ответ 1

Хотя classmethod и staticmethod очень похожи, есть небольшая разница в использовании для обоих объектов: classmethod должен иметь ссылку на объект класса в качестве первого параметра, тогда как staticmethod может иметь никаких параметров вообще.

пример

class Date(object):

    def __init__(self, day=0, month=0, year=0):
        self.day = day
        self.month = month
        self.year = year

    @classmethod
    def from_string(cls, date_as_string):
        day, month, year = map(int, date_as_string.split('-'))
        date1 = cls(day, month, year)
        return date1

    @staticmethod
    def is_date_valid(date_as_string):
        day, month, year = map(int, date_as_string.split('-'))
        return day <= 31 and month <= 12 and year <= 3999

date2 = Date.from_string('11-09-2012')
is_date = Date.is_date_valid('11-09-2012')

объяснение

Предположим, например, пример класса, связанного с информацией о дате (это будет наш шаблон):

class Date(object):

    def __init__(self, day=0, month=0, year=0):
        self.day = day
        self.month = month
        self.year = year

Этот класс, очевидно, может быть использован для хранения информации о некоторых датах (без информации о часовом поясе, и пусть все даты представлены в формате UTC).

Здесь у нас есть __init__, типичный инициализатор экземпляров класса Python, который получает аргументы как типичный метод instancemethod, имеющий первый необязательный аргумент (self), который содержит ссылку на вновь созданный экземпляр.

Метод класса

У нас есть некоторые задачи, которые можно выполнить с помощью classmethod.

Предположим, что мы хотим создать много экземпляров класса Date имеющих информацию о дате, поступающую из внешнего источника, закодированного как строка с форматом 'dd-mm-yyyy'. Предположим, что мы должны делать это в разных местах исходного кода нашего проекта.

Итак, что мы должны сделать здесь:

  1. Разбирайте строку, чтобы получать день, месяц и год в виде трех целых переменных или 3-элементного кортежа, состоящего из этой переменной.
  2. Создайте Date, передав эти значения для вызова инициализации.

Это будет выглядеть так:

day, month, year = map(int, string_date.split('-'))
date1 = Date(day, month, year)

Для этой цели C++ может реализовать такую функцию с перегрузкой, но Python не имеет этой перегрузки. Вместо этого мы можем использовать classmethod. Позвольте создать другой "конструктор".

    @classmethod
    def from_string(cls, date_as_string):
        day, month, year = map(int, date_as_string.split('-'))
        date1 = cls(day, month, year)
        return date1

date2 = Date.from_string('11-09-2012')

Давайте посмотрим более внимательно на вышеупомянутую реализацию и рассмотрим, какие преимущества у нас есть здесь:

  1. Мы реализовали синтаксический анализ строк даты в одном месте и теперь снова можно использовать повторно.
  2. Инкапсуляция отлично работает здесь (если вы считаете, что вы можете реализовать синтаксический анализ строк как одну функцию в другом месте, это решение гораздо лучше подходит к парадигме ООП).
  3. cls - это объект, который содержит сам класс, а не экземпляр класса. Это довольно круто, потому что, если мы наследуем наш класс Date, у всех детей будет также определен параметр from_string.

Статический метод

Как насчет staticmethod? Он очень похож на classmethod но не принимает никаких обязательных параметров (например, метод класса или метод экземпляра).

Давайте посмотрим на следующий вариант использования.

У нас есть строка даты, которую мы хотим как-то проверить. Эта задача также логически связана с классом Date мы использовали до сих пор, но не требует его создания.

Вот где staticmethod может быть полезным. Давайте посмотрим на следующий фрагмент кода:

    @staticmethod
    def is_date_valid(date_as_string):
        day, month, year = map(int, date_as_string.split('-'))
        return day <= 31 and month <= 12 and year <= 3999

    # usage:
    is_date = Date.is_date_valid('11-09-2012')

Таким образом, как мы можем видеть из использования staticmethod, у нас нет никакого доступа к тому, что представляет собой класс: это в основном просто функция, называемая синтаксически подобной методу, но без доступа к объекту и его внутренним элементам (полям и другие методы), в то время как classmethod делает.

Ответ 2

Ростислав Дзинко ответ очень уместен. Я подумал, что могу выделить еще одну причину, по которой вы должны выбирать @classmethod вместо @staticmethod при создании дополнительного конструктора.

В приведенном выше примере Ростислав использовал @classmethod from_string в качестве Фабрики для создания объектов Date из неприемлемых в противном случае параметров. То же самое можно сделать с @staticmethod, как показано в коде ниже:

class Date:
  def __init__(self, month, day, year):
    self.month = month
    self.day   = day
    self.year  = year


  def display(self):
    return "{0}-{1}-{2}".format(self.month, self.day, self.year)


  @staticmethod
  def millenium(month, day):
    return Date(month, day, 2000)

new_year = Date(1, 1, 2013)               # Creates a new Date object
millenium_new_year = Date.millenium(1, 1) # also creates a Date object. 

# Proof:
new_year.display()           # "1-1-2013"
millenium_new_year.display() # "1-1-2000"

isinstance(new_year, Date) # True
isinstance(millenium_new_year, Date) # True

Таким образом, оба new_year и millenium_new_year являются экземплярами класса Date.

Но, если вы внимательно наблюдаете, процесс Factory жестко запрограммирован для создания объектов Date, несмотря ни на что. Это означает, что даже если класс Date является подклассом, подклассы все равно будут создавать простые объекты Date (без каких-либо свойств подкласса). Посмотрите это в приведенном ниже примере:

class DateTime(Date):
  def display(self):
      return "{0}-{1}-{2} - 00:00:00PM".format(self.month, self.day, self.year)


datetime1 = DateTime(10, 10, 1990)
datetime2 = DateTime.millenium(10, 10)

isinstance(datetime1, DateTime) # True
isinstance(datetime2, DateTime) # False

datetime1.display() # returns "10-10-1990 - 00:00:00PM"
datetime2.display() # returns "10-10-2000" because it not a DateTime object but a Date object. Check the implementation of the millenium method on the Date class for more details.

datetime2 не является экземпляром DateTime? WTF? Ну, это из-за используемого декоратора @staticmethod.

В большинстве случаев это нежелательно. Если вам нужен метод Factory, который знает класс, который его вызвал, то вам нужен @classmethod.

Переписать Date.millenium как (что единственная часть вышеуказанного кода, который изменяется):

@classmethod
def millenium(cls, month, day):
    return cls(month, day, 2000)

гарантирует, что class не жестко запрограммирован, а скорее изучен. cls может быть любым подклассом. Результирующий object по праву будет экземпляром cls.
Давайте проверим это:

datetime1 = DateTime(10, 10, 1990)
datetime2 = DateTime.millenium(10, 10)

isinstance(datetime1, DateTime) # True
isinstance(datetime2, DateTime) # True


datetime1.display() # "10-10-1990 - 00:00:00PM"
datetime2.display() # "10-10-2000 - 00:00:00PM"

Причина, как вы уже знаете, в том, что вместо @staticmethod использовался @classmethod

Ответ 3

@classmethod означает: когда этот метод вызывается, мы передаем класс как первый аргумент вместо экземпляра этого класса (как обычно мы делаем с методами). Это означает, что вы можете использовать класс и его свойства внутри этого метода, а не конкретный экземпляр.

@staticmethod означает: когда вызывается этот метод, мы не передаем ему экземпляр класса (как это обычно делается с методами). Это означает, что вы можете поместить функцию внутри класса, но вы не можете получить доступ к экземпляру этого класса (это полезно, когда ваш метод не использует экземпляр).

Ответ 4

Когда использовать каждый

Функция @staticmethod - это не что иное, как функция, определенная внутри класса. Он вызывается без создания экземпляра класса первым. Его определение является неизменным через наследование.

  • Python не должен создавать экземпляр метода привязки для объекта.
  • Это облегчает читабельность кода: видя @staticmethod, мы знаем, что метод не зависит от состояния самого объекта;

Функция @classmethod также может @classmethod без создания экземпляра класса, но ее определение следует за подклассом, а не родительский класс посредством наследования может быть переопределен подклассом. Это потому, что первый аргумент для функции @classmethod всегда должен быть cls (class).

  • Методы фабрики, которые используются для создания экземпляра для класса, используя, например, некоторую предварительную обработку.
  • Статические методы, вызывающие статические методы: если вы разделяете статические методы на несколько статических методов, вам не нужно жестко кодировать имя класса, а использовать методы класса

вот хорошая ссылка на эту тему.

Ответ 5

Значение @classmethod и @staticmethod?

  • Метод - это функция в пространстве имен объектов, доступная как атрибут.
  • Обычный (то есть экземпляр) метод получает экземпляр (мы обычно называем его self) как неявный первый аргумент.
  • Метод класса получает класс (мы обычно называем его cls) как неявный первый аргумент.
  • Статический метод не получает неявного первого аргумента (например, регулярной функции).

когда я должен их использовать, почему я должен их использовать и как их использовать?

Вам не нужен ни декоратор. Но по принципу, что вы должны минимизировать количество аргументов функций (см. "Чистый кодер"), они полезны для этого.

class Example(object):

    def regular_instance_method(self):
        """A function of an instance has access to every attribute of that 
        instance, including its class (and its attributes.)
        Not accepting at least one argument is a TypeError.
        Not understanding the semantics of that argument is a user error.
        """
        return some_function_f(self)

    @classmethod
    def a_class_method(cls):
        """A function of a class has access to every attribute of the class.
        Not accepting at least one argument is a TypeError.
        Not understanding the semantics of that argument is a user error.
        """
        return some_function_g(cls)

    @staticmethod
    def a_static_method():
        """A static method has no information about instances or classes
        unless explicitly given. It just lives in the class (and thus its 
        instances') namespace.
        """
        return some_function_h()

Для обоих методов экземпляра и методов класса, не принимая хотя бы один аргумент, является TypeError, но не понимая семантики этого аргумента, это ошибка пользователя.

(Определите some_function, например:

some_function_h = some_function_g = some_function_f = lambda x=None: x

и это будет работать.)

пунктирный поиск по экземплярам и классам:

Точный поиск экземпляра выполняется в этом порядке - мы ищем:

  1. дескриптор данных в пространстве имен классов (например, свойство)
  2. данных в экземпляре __dict__
  3. дескриптор без данных в пространстве имен классов (методы).

Обратите внимание, что пунктирный поиск экземпляра вызывается следующим образом:

instance = Example()
instance.regular_instance_method 

и методы являются вызываемыми атрибутами:

instance.regular_instance_method()

методы экземпляра

Аргумент, self, неявно задается через пунктирный поиск.

Вы должны обращаться к методам экземпляра из экземпляров класса.

>>> instance = Example()
>>> instance.regular_instance_method()
<__main__.Example object at 0x00000000399524E0>

методы класса

Аргумент cls неявно задается путем точечного поиска.

Вы можете получить доступ к этому методу через экземпляр или класс (или подклассы).

>>> instance.a_class_method()
<class '__main__.Example'>
>>> Example.a_class_method()
<class '__main__.Example'>

статические методы

Никакие аргументы не указаны. Этот метод работает как любая функция, определенная (например) в пространстве имен модулей, за исключением того, что ее можно искать

>>> print(instance.a_static_method())
None

Опять же, когда я должен их использовать, почему я должен их использовать?

Каждый из них постепенно более ограничивает информацию, передаваемую методом методом метода экземпляра.

Используйте их, когда вам не нужна информация.

Это упрощает определение ваших функций и методов и их устранение.

О чем легче рассуждать?

def function(x, y, z): ...

или

def function(y, z): ...

или

def function(z): ...

Функции с меньшим количеством аргументов легче рассуждать. Их также легче отключить.

Они сродни экземпляру, классу и статическим методам. Помня о том, что, когда у нас есть пример, у нас также есть свой класс, снова спросите себя, о чем легче рассуждать?:

def an_instance_method(self, arg, kwarg=None):
    cls = type(self)             # Also has the class of instance!
    ...

@classmethod
def a_class_method(cls, arg, kwarg=None):
    ...

@staticmethod
def a_static_method(arg, kwarg=None):
    ...

Встроенные примеры

Вот пара моих любимых встроенных примеров:

Статический метод str.maketrans был функцией в string модуле, но гораздо удобнее для него быть доступным из пространства имен str.

>>> 'abc'.translate(str.maketrans({'a': 'b'}))
'bbc'

Метод dict.fromkeys возвращает новый словарь, созданный с помощью итерации ключей:

>>> dict.fromkeys('abc')
{'a': None, 'c': None, 'b': None}

При подклассе мы видим, что он получает информацию о классе как метод класса, что очень полезно:

>>> class MyDict(dict): pass
>>> type(MyDict.fromkeys('abc'))
<class '__main__.MyDict'> 

Мой совет - Заключение

Используйте статические методы, когда вам не нужны аргументы класса или экземпляра, но функция связана с использованием объекта, и функция удобна в пространстве имен объектов.

Используйте методы класса, когда вам не нужна информация об экземпляре, но вам нужна информация о классе, возможно, для другого класса или статических методов или, возможно, сама по себе как конструктор. (Вы не будете жестко кодировать класс, чтобы подклассы могли использоваться здесь.)

Ответ 6

Можно использовать @classmethod, когда он захочет изменить поведение метода на основе того, какой подкласс вызывает метод. помните, что у нас есть ссылка на вызывающий класс в методе класса.

При использовании static вы хотите, чтобы поведение оставалось неизменным по подклассам

Пример:

class Hero:

  @staticmethod
  def say_hello():
     print("Helllo...")

  @classmethod
  def say_class_hello(cls):
     if(cls.__name__=="HeroSon"):
        print("Hi Kido")
     elif(cls.__name__=="HeroDaughter"):
        print("Hi Princess")

class HeroSon(Hero):
  def say_son_hello(self):
     print("test  hello")



class HeroDaughter(Hero):
  def say_daughter_hello(self):
     print("test  hello daughter")


testson = HeroSon()

testson.say_class_hello() #Output: "Hi Kido"

testson.say_hello() #Outputs: "Helllo..."

testdaughter = HeroDaughter()

testdaughter.say_class_hello() #Outputs: "Hi Princess"

testdaughter.say_hello() #Outputs: "Helllo..."

Ответ 7

Небольшая компиляция

@staticmethod Способ записи метода внутри класса без ссылки на объект, на который он вызывается. Поэтому нет необходимости передавать неявный аргумент, например self или cls. Это написано точно так же, как написано вне класса, но это не бесполезно для python, потому что если вам нужно инкапсулировать метод внутри класса, так как этот метод должен быть частью этого класса, то метод @staticmethod удобен в том, что случай.

@classmethod Важно, когда вы хотите написать метод factory, и этот пользовательский атрибут может быть присоединен к классу. Этот атрибут может быть переопределен в унаследованном классе.

Сравнение этих двух методов может быть ниже

Таблица

Ответ 8

Короче говоря, @classmethod превращает обычный метод в фабричный метод.

Давайте рассмотрим это на примере:

class PythonBook:
    def __init__(self, name, author):
        self.name = name
        self.author = author
    def __repr__(self):
        return f'Book: {self.name}, Author: {self.author}'

Без @classmethod вы должны трудиться, чтобы создавать экземпляры один за другим, и они разбросаны.

book1 = PythonBook('Learning Python', 'Mark Lutz')
In [20]: book1
Out[20]: Book: Learning Python, Author: Mark Lutz
book2 = PythonBook('Python Think', 'Allen B Dowey')
In [22]: book2
Out[22]: Book: Python Think, Author: Allen B Dowey

Как, например, с @classmethod

class PythonBook:
    def __init__(self, name, author):
        self.name = name
        self.author = author
    def __repr__(self):
        return f'Book: {self.name}, Author: {self.author}'
    @classmethod
    def book1(cls):
        return cls('Learning Python', 'Mark Lutz')
    @classmethod
    def book2(cls):
        return cls('Python Think', 'Allen B Dowey')

Попробуй это:

In [31]: PythonBook.book1()
Out[31]: Book: Learning Python, Author: Mark Lutz
In [32]: PythonBook.book2()
Out[32]: Book: Python Think, Author: Allen B Dowey

Увидеть? Экземпляры успешно создаются внутри определения класса и собираются вместе.

В заключение, декоратор @classmethod преобразует обычный метод в фабричный метод. Использование методов класса позволяет добавлять столько альтернативных конструкторов, сколько необходимо.

Ответ 9

Я новичок на этом сайте, я прочитал все вышеперечисленные ответы и получил информацию, что хочу. Однако у меня нет права на повышение. Поэтому я хочу начать с StackOverflow с ответом, насколько я понимаю.

  • @staticmethod не требуется self или cls в качестве первого параметра метода
  • @staticmethod и @classmethod заверенная функция может быть вызвана переменной экземпляра или класса
  • @staticmethod украшенная функция влияет на какое-то "неизменяемое свойство", что наследование подкласса не может перезаписывать функцию базового класса, которая обертывается декорером @staticmethod.
  • @classmethod требуется cls (имя класса, вы можете изменить имя переменной, если хотите, но это не рекомендуется) в качестве первого параметра функции
  • @classmethod всегда используется подклассовым способом, наследование подкласса может изменять эффект функции базового класса, т.е. @classmethod функция обернутого базового класса может быть перезаписана различными подклассами.

Ответ 10

@classmethod

@classmethod можно сравнить с __init__. Вы можете подумать, что это другой __init__(). Именно так Python реализует перегрузку конструктора класса в c++.

class C:
    def __init__(self, parameters):
        ....

    @classmethod
    def construct_from_func(cls, parameters):
        ....

obj1 = C(parameters)
obj2 = C.construct_from_func(parameters)

обратите внимание, что они оба имеют ссылку на класс в качестве первого аргумента в определении, в то время как __init__ использует self но construct_from_func обычно использует cls.

@staticmethod

@staticmethod можно сравнить с object method

class C:
    def __init__(self):
        ....

    @staticmethod
    def static_method(args):
        ....

    def normal_method(parameters):
        ....

result = C.static_method(parameters)
result = obj.normal_method(parameters)

Ответ 11

Немного другой способ подумать об этом, который может быть полезен для кого-то... Метод класса используется в суперклассе, чтобы определить, как должен вести себя этот метод при его вызове с помощью разных дочерних классов. Статический метод используется, когда мы хотим вернуть одно и то же, независимо от дочернего класса, который мы вызываем.

Ответ 12

Метод класса может модифицировать состояние класса, он связан с классом и содержит параметр cls в качестве параметра.

Статический метод не может изменять состояние класса, он связан с классом и не знает класс или экземпляр

class empDetails:
    def __init__(self,name,sal):
        self.name=name
        self.sal=sal
    @classmethod
    def increment(cls,name,none):
        return cls('yarramsetti',6000 + 500)
    @staticmethod
    def salChecking(sal):
        return sal > 6000

emp1=empDetails('durga prasad',6000)
emp2=empDetails.increment('yarramsetti',100)
# output is 'durga prasad'
print emp1.name
# output put is 6000
print emp1.sal
# output is 6500,because it change the sal variable
print emp2.sal
# output is 'yarramsetti' it change the state of name variable
print emp2.name
# output is True, because ,it change the state of sal variable
print empDetails.salChecking(6500)