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

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

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

#1 楼

尽管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),该参数持有对新创建的实例的引用。

Class Method

我们有一些可以使用classmethod s很好地完成的任务。

假设我们要创建很多q4312 079q类实例,其日期信息来自外部源,其日期信息编码为字符串,格式为“ dd-mm-yyyy”。假设我们必须在项目的源代码中的不同位置执行此操作。

所以我们在这里必须做的是:


解析字符串以接收日,月和年为三个整数变量或由该变量组成的三项元组。
通过将这些值传递给初始化调用来实例化Date

看起来像:为此,C ++可以通过重载实现这种功能,但是Python缺少这种重载。相反,我们可以使用Date。让我们创建另一个“构造函数”。

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


让我们更仔细地看一下上述实现,并在这里回顾一下我们具有的优势:


我们已经在一个地方实现了日期字符串解析,现在可以重用了。
封装在这里可以很好地工作(如果您认为可以将字符串解析作为其他函数实现为其他功能,则此解决方案将更好地适合OOP范例)。

classmethod是保存类本身的对象,而不是保存类的对象类的实例。这很酷,因为如果我们继承cls类,则所有子代也会定义Date

静态方法

from_string怎么样?它与staticmethod非常相似,但不带任何强制性参数(像类方法或实例方法一样)。

让我们看下一个用例。

我们有我们想要以某种方式验证的日期字符串。从逻辑上讲,此任务也已绑定到我们到目前为止使用的classmethod类,但不需要实例化。

这里是Date有用的地方。让我们看下一段代码:

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


因此,正如我们从staticmethod的用法可以看到的那样,我们无权访问该类是什么- -它基本上只是一个函数,在语法上像方法一样被调用,但是不能访问对象及其内部(字段和其他方法),而classmethod可以。

评论


“ from_string”方法将“ Date”类(不是Date对象)作为第一个参数“ cls”,并通过调用cls(day,month,year)返回构造函数,该方法等效于Date(day,month,year)和返回一个Date对象。

–巴胡巴里语Patil
19年6月2日在10:30



那么可以将类方法视为“替代构造函数”吗?我认为这是迄今为止最好的解释!

– Cryptoharf84
19年8月28日在17:45

对于您的类方法示例,您是否无法通过将from_string定义为字符串方法而不是类方法来完全完成上述操作,然后不调用cls(day,month,year),而是调用Date(day,月,年)?我想这里使用类方法的唯一好处是,如果您希望该方法可用于可能继承并期望from_string对继承的类起作用的子类,对吗?还是我错过了您的要点?

–乔什
4月26日在3:55



@ Josh--stringmethod需要一个现有的对象来工作,所以您不能做同样的事情。正如Crypoharf84所提到的,这允许构造对象的另一种机制。这似乎类似于Dart语言中的“命名构造函数”,后者提供了用于为同一类创建多个构造函数的机制。不同的构造函数允许轻松创建API,例如Date.from_json,Date.from_sql,Date.from_file等。

– DarrylG
5月28日19:53

#2 楼

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

在上面的示例中,Rostyslav使用@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_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

评论


在解释@classmethod的作用时,说它实际上是工厂方法最有用。

–t3chb0t
18-11-3在17:29



什么是cls?按照上面的答案-cl可以是任何子类。结果对象将正确地是cls的实例。 cls是调用Date或Datetime的对象或方法吗?请解释。

– Rishi jain
19-10-15在11:14

@rishijain就像self如何引用实例一样,cls引用类-例如cls(month,day,2000)== DateTime(month,day,2000)

–同胞
4月20日11:48



这个答案确实阐明了@classmethod对于支持继承的工厂方法的实用性:创建带有一些预处理等的调用类的实例。

–Christabella Irwanto
7月2日8:46



#3 楼

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

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

#4 楼

何时使用每个

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


Python不必实例化对象的绑定方法。
它简化了代码的可读性:看到@staticmethod,我们知道该方法不依赖于对象本身的状态; <​​br />
@classmethod函数也可以在不实例化该类的情况下调用,但是其定义遵循子类,而不是父类(通过继承),可以被子类覆盖。这是因为@classmethod函数的第一个参数必须始终是cls (class)



工厂方法,这些方法用于使用例如某种预处理。

静态方法调用静态方法:如果将静态方法拆分为多个静态方法,则不应硬编码类名,而应使用类方法

这里很好链接到该主题。

评论


感谢您比公认的答案更快地达到目标。

– John R Perry
19年8月22日在1:54

#5 楼


@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

点缀对实例和类的查找:
按此顺序对实例进行点查找-我们寻找:

类命名空间中的数据描述符(如属性)
__dict__实例中的数据

类名称空间中的非数据描述符(方法)。

请注意,实例上的点分查找是这样调用的:
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'> 

我的建议-结论
当不需要类或实例参数,但该函数与对象的使用有关时,请使用静态方法,并且该函数位于对象的名称空间中很方便。
当您不需要实例信息但可能需要其类或其他统计信息的类信息时,请使用类方法ic方法,或者本身就是构造函数。 (您不会对类进行硬编码,因此可以在此处使用子类。)

评论


通过简洁的解释和微观示例之间的恰当平衡,这是非常清楚的。

– abalter
18 Mar 21 '18 at 10:43

当您不需要实例信息,但可能需要其类的其他类或静态方法,或者本身需要作为构造函数时,请使用类方法。 (您不会对类进行硬编码,以便可以在此处使用子类。)正是我想要的。我将使用实际答案的原因。

–TheRealFakeNews
18-10-11在23:53

这是最好的解释。谢谢!

–piby180
19年8月20日在8:27

#6 楼

当他/她想根据哪个子类正在调用方法来更改方法的行为时,可以使用@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..."


#7 楼

少量编译

@staticmethod
一种在类内编写方法而无需引用被调用对象的方法。因此,无需传递诸如self或cls之类的隐式参数。
它的写法与在类外部的写法完全相同,但是在python中并没有用,因为自此以来,如果您需要将方法封装在类中方法必须是该类的一部分,在这种情况下@staticmethod会派上用场。

@classmethod
当您要编写工厂方法并通过此自定义属性来使用时,这一点很重要)可以附加在一个类中。可以在继承的类中重写此属性。

这两种方法之间的比较如下所示



#8 楼

@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__通常使用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)


#9 楼

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



@staticmethod不需要self或cls作为方法的第一个参数

@staticmethod@classmethod包装的函数可以由实例或类变量调用

@staticmethod装饰函数会影响子类继承不能覆盖其基类函数的某种“不可变属性”由@staticmethod装饰器包裹。

@classmethod需要cls(类名,如果需要,可以更改变量名,但不建议使用)作为函数的第一个参数

@classmethod始终以子类方式使用,子类继承可能会改变基类函数的效果,即@classmethod包装的基类函数可能会被不同的子类覆盖。


评论


我不确定在这种情况下不可变是什么意思?

–菲利普·阿德勒(Philip Adler)
17年7月23日在11:59

#10 楼

思考它的方式略有不同,这可能对某人有用...类方法用于超类中,以定义当不同的子类调用该方法时应如何表现。无论我们要调用的子类如何,当我们要返回相同的内容时,将使用静态方法。

#11 楼

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

评论


如果您创建没有类方法的方法,仍然可以达到相同的结果,这不是真正的区别

– Shailyn Ortiz
18年1月1日在17:10

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