Вопрос: Значение @classmethod и @staticmethod для начинающих?


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

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

ТЛ; др: когда следует ли использовать их, Зачем следует использовать их и как следует ли использовать их?

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


1216


источник


Ответы:


Хоть 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), который содержит ссылку на вновь созданный экземпляр.

Метод класса

У нас есть некоторые задачи, которые можно classmethods.

Предположим, что мы хотим создать много 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. Инкапсуляция отлично работает здесь (если вы считаете, что вы можете реализовать синтаксический анализ строк как одну функцию в другом месте, это решение намного лучше подходит для OOP-парадигмы).
  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, у нас нет никакого доступа к классу - это в основном просто функция, называемая синтаксически подобной методу, но без доступа к объекту и его внутренним элементам (полям и другим методам), в то время как класс метод.


2110



Ответ Ростислава Дзинько очень уместен. Я подумал, что могу выделить еще одну причину, которую вы должны выбрать @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класс.

Но, если вы внимательно наблюдаете, фабричный процесс жестко закодирован для создания 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


698



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

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


193



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

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

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

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

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


51



One would use @classmethod when he/she would want to change the behaviour of the method based on which subclass is calling the method. remember we have a reference to the calling class in a class method.

While using static you would want the behaviour to remain unchanged across subclasses

Example:

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..."

27



A little compilation

@staticmethod A way to write a method inside a class without reference to the object it is being called on. So no need to pass implicit argument like self or cls. It is written exactly the same how written outside the class, but it is not of no use in python because if you need to encapsulate a method inside a class since this method needs to be the part of that class @staticmethod is comes handy in that case.

@classmethod It is important when you want to write a factory method and by this custom attribute(s) can be attached in a class. This attribute(s) can be overridden in the inherited class.

A comparison between these two methods can be as below

Table


26



Meaning of @classmethod and @staticmethod?

  • A method is a function in an object's namespace, accessible as an attribute.
  • A regular (i.e. instance) method gets the instance (we usually call it self) as the implicit first argument.
  • A class method gets the class (we usually call it cls) as the implicit first argument.
  • A static method gets no implicit first argument (like a regular function).

when should I use them, why should I use them, and how should I use them?

You don't need either decorator. But on the principle that you should minimize the number of arguments to functions (see Clean Coder), they are useful for doing just that.

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

For both instance methods and class methods, not accepting at least one argument is a TypeError, but not understanding the semantics of that argument is a user error.

(Define some_function's, e.g.:

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

and this will work.)

dotted lookups on instances and classes:

A dotted lookup on an instance is performed in this order - we look for:

  1. a data descriptor in the class namespace (like a property)
  2. data in the instance __dict__
  3. a non-data descriptor in the class namespace (methods).

Note, a dotted lookup on an instance is invoked like this:

instance = Example()
instance.regular_instance_method 

and methods are callable attributes:

instance.regular_instance_method()

instance methods

The argument, self, is implicitly given via the dotted lookup.

You must access instance methods from instances of the class.

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

class methods

The argument, cls, is implicitly given via dotted lookup.

You can access this method via an instance or the class (or subclasses).

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

static methods

No arguments are implicitly given. This method works like any function defined (for example) on a modules' namespace, except it can be looked up

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

Again, when should I use them, why should I use them?

Each of these are progressively more restrictive in the information they pass the method versus instance methods.

Use them when you don't need the information.

This makes your functions and methods easier to reason about and to unittest.

Which is easier to reason about?

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

or

def function(y, z): ...

or

def function(z): ...

The functions with fewer arguments are easier to reason about. They are also easier to unittest.

These are akin to instance, class, and static methods. Keeping in mind that when we have an instance, we also have its class, again, ask yourself, which is easier to reason about?:

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):
    ...

Builtin examples

Here are a couple of my favorite builtin examples:

The str.maketrans static method was a function in the string module, but it is much more convenient for it to be accessible from the str namespace.

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

The dict.fromkeys class method returns a new dictionary instantiated from an iterable of keys:

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

When subclassed, we see that it gets the class information as a class method, which is very useful:

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

My advice - Conclusion

Use static methods when you don't need the class or instance arguments, but the function is related to the use of the object, and it is convenient for the function to be in the object's namespace.

Use class methods when you don't need instance information, but need the class information perhaps for its other class or static methods, or perhaps itself as a constructor. (You wouldn't hardcode the class so that subclasses could be used here.)


14



I'm a beginner on this site, I have read all above answers, and got the information what I want. However, I don't have the right to upvote. So I want to get my start on StackOverflow with the answer as I understand it.

  • @staticmethod doesn't need self or cls as the first parameter of the method
  • @staticmethod and @classmethod wrapped function could be called by instance or class variable
  • @staticmethod decorated function impact some kind 'immutable property' that subclass inheritance can't overwrite its base class function which is wrapped by a @staticmethod decorator.
  • @classmethod need cls (Class name, you could change the variable name if you want, but it's not advised) as the first parameter of function
  • @classmethod always used by subclass manner, subclass inheritance may change the effect of base class function, i.e. @classmethod wrapped base class function could be overwritten by different subclasses.

1



A slightly different way to think about it that might be useful for someone... A class method is used in a superclass to define how that method should behave when it's called by different child classes. A static method is used when we want to return the same thing regardless of the child class that we are calling.


0



In short, @classmehtod turns a normal method to a factory method.

Let's explore it with an example:

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

Without a @classmethod,you should labor to creat instances one by one and they are scartted.

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

As for example with @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')

Test it:

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

See? Instances are successfully created inside a class definition and they are collected together.

In conclusion, @classmethod decorator convert a conventional method to a factory method,Using classmethods makes it possible to add as many alternative constructors as necessary.


0