Что значит @classmethod и @staticmethod для начинающих?

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

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

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

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

12 ответов

Решение

Хоть 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 s.

Давайте предположим, что мы хотим создать много 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 делает.

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

В приведенном выше примере Ростислав использовал @classmethodfrom_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 учебный класс.

Но, если вы внимательно наблюдаете, процесс фабрики жестко запрограммирован для создания 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's not a DateTime object but a Date object. Check the implementation of the millenium method on the Date class

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"

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

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

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

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

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

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

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

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

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

Значение @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'> 

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

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

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

Один будет использовать @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..."

Небольшой сборник

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

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

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

Таблица

@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)

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

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

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

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

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 преобразует обычный метод в фабричный метод. Использование методов класса позволяет добавлять столько альтернативных конструкторов, сколько необходимо.

Метод класса может изменять состояние класса, он связан с классом и содержит 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)

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

Другие вопросы по тегам