@classmethod 和 @staticmethod 对初学者的意义?

有人可以向我解释 python 中@classmethod@staticmethod的含义吗?我需要知道区别和含义。

据我了解, @classmethod告诉类它是一个应该继承到子类中的方法,或者...。但是,这有什么意义呢?为什么不只定义类方法而不添加@classmethod@staticmethod或任何@定义呢?

tl; dr:什么时候应该使用它们, 为什么要使用它们,以及如何使用它们?

我对 C ++ 非常了解,因此使用更高级的编程概念应该不是问题。如果可能,请给我相应的 C ++ 示例。

答案

尽管classmethodstaticmethod非常相似,但两个实体的用法还是有一点不同: 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. 解析字符串以将日,月和年作为三个整数变量或由该变量组成的三项元组接收。
  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非常相似,但不包含任何强制性参数(就像 class 方法或 instance 方法一样)。

让我们看下一个用例。

我们有一个日期字符串,我们想以某种方式进行验证。从逻辑上讲,此任务还绑定到我们到目前为止使用的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 确实可以。

Rostyslav Dzinko 的答案非常恰当。我以为我可以强调另一个原因, @staticmethod在创建其他构造函数时应该选择@classmethod不是@staticmethod

在上面的示例中,Rostyslav 使用@classmethod from_string作为 Factory 来从其他不可接受的参数中创建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_yearmillenium_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's 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"

如您现在所知, @classmethod是使用@staticmethod而不是@staticmethod

@classmethod意思是:调用此方法时,我们将类作为第一个参数传递,而不是该类的实例(通常使用方法)。这意味着您可以在该方法而不是特定实例中使用类及其属性。

@staticmethod意思是:调用此方法时,我们不会将类的实例传递给它(通常与方法一样)。这意味着您可以在一个类中放置一个函数,但是不能访问该类的实例(这在您的方法不使用该实例时非常有用)。

什么时候使用

@staticmethod函数不过是在类内部定义的函数。可以调用而无需先实例化该类。它的定义通过继承是不可变的。

  • Python 不必实例化对象的绑定方法
  • 它简化了代码的可读性:看到@staticmethod ,我们知道该方法不依赖于对象本身的状态。

@classmethod函数也可以在不实例化类的情况下调用,但其定义遵循子类,而不是父类,通过继承可以被子类覆盖。这是因为@classmethod函数的第一个参数必须始终为cls (class)

  • 工厂方法 ,用于使用例如某种预处理为类创建实例。
  • 静态方法调用静态方法 :如果将静态方法拆分为多个静态方法,则不应硬编码类名,而应使用类方法

是该主题的很好链接。

@classmethod@staticmethod含义?

  • 方法是对象名称空间中的函数,可以作为属性访问。
  • 常规(即实例)方法将实例(我们通常称其为self )作为隐式第一个参数。
  • 方法将类(通常称为cls )作为隐式第一个参数。
  • 静态方法不会获取任何隐式第一个参数(例如常规函数)。

我什么时候应该使用它们,为什么要使用它们,以及如何使用它们?

不需要任何一个装饰器。但是基于应尽量减少函数参数的数量的原则(请参阅 Clean Coder),它们对于执行此操作很有用。

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.maketransstring模块中的一个函数,但是从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 。记住我们在类方法中有一个对调用类的引用。

使用静态时,您希望行为在子类之间保持不变

例:

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当您要编写工厂方法并且通过此自定义属性可以附加在类中时,这一点很重要。可以在继承的类中重写此属性。

这两种方法的比较如下

表

我是该网站的初学者,已经阅读了以上所有答案,并获得了所需的信息。但是,我无权投票。所以我想从我了解的答案开始使用 StackOverflow。

  • @staticmethod不需要 self 或 cls 作为方法的第一个参数
  • @staticmethod@classmethod包装的函数可以由实例或类变量调用
  • @staticmethod装饰函数会影响某种 “不可变属性”,即子类继承不能覆盖由@staticmethod装饰器包装的基类函数。
  • @classmethod需要 cls(类名,如果需要,可以更改变量名,但不建议使用)作为函数的第一个参数
  • @classmethod始终以子类方式使用,子类继承可能会改变基类函数的效果,即, @classmethod包装的基类函数可能会被不同的子类覆盖。

简而言之,@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 装饰器将常规方法转换为工厂方法,使用 classmethods 可以根据需要添加尽可能多的替代构造函数。

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

注意,它们都在 definitioin 中将类的引用作为第一个参数,而__init__使用selfconstruct_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)