对于初学者来说,@类方法和@静态方法的含义

@classmethod@staticmethod在Python中是什么意思,它们有什么不同?我应该使用它们,为什么我应该使用它们,如何我应该使用它们吗?

据我所知,@classmethod告诉一个类它是一个应该被继承到子类中的方法,或者……什么的。然而,这有什么意义?为什么不直接定义类方法,而不添加@classmethod@staticmethod或任何@定义?

747873 次浏览

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

@staticmethod表示:当调用这个方法时,我们不会将类的实例传递给它(就像我们通常对方法所做的那样)。这意味着你可以将一个函数放在类中,但你不能访问该类的实例(当你的方法不使用实例时,这很有用)。

虽然classmethodstaticmethod非常相似,但这两个实体的用法略有不同:classmethod必须将对类对象的引用作为第一个参数,而staticmethod可以根本没有参数。

示例

class Date(object):    
def __init__(self, day=0, month=0, year=0):self.day = dayself.month = monthself.year = year
@classmethoddef from_string(cls, date_as_string):day, month, year = map(int, date_as_string.split('-'))date1 = cls(day, month, year)return date1
@staticmethoddef 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 = dayself.month = monthself.year = year

此类显然可用于存储有关某些日期的信息(没有时区信息;让我们假设所有日期都以UTC表示)。

这里我们有__init__,Python类实例的典型初始化器,它接收参数作为典型的实例方法,具有第一个非可选参数(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。让我们创建另一个构造函数

    @classmethoddef 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可能有用的地方。让我们看下一段代码:

    @staticmethoddef 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的回答非常恰当。我想我可以强调另一个原因,当您创建一个额外的构造函数时,您应该选择@classmethod而不是@staticmethod

示例中,Rostyslav使用@classmethodfrom_string作为工厂从其他不可接受的参数创建Date对象。@staticmethod也可以这样做,如下面的代码所示:

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

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

@staticmethoddef millenium(month, day):return Date(month, day, 2000)
new_year = Date(1, 1, 2013)               # Creates a new Date objectmillenium_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) # Trueisinstance(millenium_new_year, Date) # True

因此,new_yearmillenium_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) # Trueisinstance(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重写为(这是上面代码中唯一更改的部分):

@classmethoddef 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) # Trueisinstance(datetime2, DateTime) # True

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

原因是,正如你现在所知道的,使用了@classmethod而不是@staticmethod

何时使用每个

@staticmethod函数只不过是在类中定义的函数。无需先实例化类即可调用。它的定义通过继承是不可变的。

  • Python不必为对象实例化界限法
  • 它简化了代码的易读性:看到@陈志立,我们知道该方法不依赖于对象本身的状态;

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

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

这里是这个主题的好链接。

一个小汇编

@静态方法一种在类中编写方法而不引用它被调用的对象的方法。因此不需要传递像self或cls这样的隐式参数。它的编写方式与在类外编写的方式完全相同,但它在python中并非没有用处,因为如果您需要在类内封装一个方法,因为此方法需要成为该类的一部分@static方法在这种情况下很方便。

@类方法当您想编写工厂方法并且可以通过此自定义属性附加到类中时,这很重要。此属性可以在继承的类中被覆盖。

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

Table

当他/她想要根据哪个子类调用方法来更改方法的行为时,会使用@classmethod。记住我们在类方法中有对调用类的引用。

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

示例:

class Hero:
@staticmethoddef say_hello():print("Helllo...")
@classmethoddef 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..."

一种稍微不同的思考方式,可能对某人有用…超类中使用类方法来定义该方法在被不同的子类调用时的行为。当我们想要返回相同的东西而不管我们调用的子类时,就会使用静态方法。

我是这个网站的初学者,我已经阅读了上述所有答案,并得到了我想要的信息。然而,我没有投票的权利。所以我想用我理解的答案开始我的StackOverflow。

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

类方法可以修改类状态,它绑定到类并包含cls作为参数。

静态方法不能修改类状态,它绑定到类上,不知道类或实例

class empDetails:def __init__(self,name,sal):self.name=nameself.sal=sal@classmethoddef increment(cls,name,none):return cls('yarramsetti',6000 + 500)@staticmethoddef 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 6000print emp1.sal# output is 6500,because it change the sal variableprint emp2.sal# output is 'yarramsetti' it change the state of name variableprint emp2.name# output is True, because ,it change the state of sal variableprint empDetails.salChecking(6500)

@classmethod@staticmethod的含义?

  • 方法是对象命名空间中的函数,可作为属性访问。
  • 常规(即实例)方法获取实例(我们通常称之为self)作为隐式第一个参数。
  • <强>类方法获取类(我们通常称之为cls)作为隐式的第一个参数。
  • 静态方法没有隐式的第一个参数(如常规函数)。

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

你也不需要装饰器。但是根据你应该最小化函数参数数量的原则(参见清洁编码器),它们对于做到这一点很有用。

class Example(object):
def regular_instance_method(self):"""A function of an instance has access to every attribute of thatinstance, 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)
@classmethoddef 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)
@staticmethoddef a_static_method():"""A static method has no information about instances or classesunless explicitly given. It just lives in the class (and thus itsinstances') 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!...
@classmethoddef a_class_method(cls, arg, kwarg=None):...
@staticmethoddef a_static_method(arg, kwarg=None):...

内置示例

以下是我最喜欢的几个内置示例:

str.maketrans静态方法是string模块中的一个函数,但从str命名空间访问它要方便得多。

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

dict.fromkeys class方法返回一个从可迭代键实例化的新字典:

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

当子类化时,我们看到它作为类方法获取类信息,这非常有用:

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

我的建议-结论

当不需要类或实例参数,但函数与对象的使用相关,并且函数位于对象的命名空间中时,请使用静态方法。

当你不需要实例信息,但需要类信息时,可以使用类方法,可能是为了它的其他类或静态方法,或者可能是作为构造函数本身。(你不会对类进行硬编码,以便可以在这里使用子类。)

简而言之,@类方法将普通方法转换为工厂方法。

让我们用一个例子来探索它:

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

如果没有@类方法,你应该努力一个接一个地创建实例,它们是分散的。

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

例如,使用@类方法

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

测试它:

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

看到了吗?实例在类定义中成功创建,并将它们收集在一起。

总之,@类方法装饰器将传统方法转换为工厂方法,使用类方法可以根据需要添加尽可能多的替代构造函数。

@吴兴龙

@classmethod可以与__init__进行比较。你可以认为这是另一个__init__()。这是python在c++中实现类构造函数重载的方式。

class C:def __init__(self, parameters):....
@classmethoddef construct_from_func(cls, parameters):....
obj1 = C(parameters)obj2 = C.construct_from_func(parameters)

请注意,它们都将class的引用作为定义中的第一个参数,而__init__使用self,但construct_from_func通常使用cls

@陈志立

@staticmethod可以与object method比较

class C:def __init__(self):....
@staticmethoddef static_method(args):....
def normal_method(parameters):....
result = C.static_method(parameters)result = obj.normal_method(parameters)