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

本问题已经有最佳答案,请猛点这里访问。

谁能给我解释一下python中@classmethod@staticmethod的含义?我需要知道它们的区别和意义。

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

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

我对c++相当精通,所以使用更高级的编程概念应该不成问题。如果可能的话,请给我一个相应的c++例子。


虽然classmethodstaticmethod非常相似,但是它们的用法略有不同:classmethod必须有对类对象的引用作为第一个参数,而staticmethod可以完全没有参数。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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')

解释

让我们假设一个类的例子,处理日期信息(这将是我们的样板文件):

1
2
3
4
5
6
class Date(object):

    def __init__(self, day=0, month=0, year=0):
        self.day = day
        self.month = month
        self.year = year

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

这里我们有一个典型的Python类实例初始化器__init__,它以典型的instancemethod接收参数,第一个非可选参数(self)持有对新创建实例的引用。

类方法

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

让我们假设我们想要创建许多Date类实例,其中包含来自外部源的日期信息,该源编码为字符串,格式为"dd-mm-yyyy"。假设我们必须在项目源代码的不同位置执行此操作。

所以我们必须做的是:

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

这看起来像:

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

为此,c++可以通过重载来实现这样的特性,但是Python没有这种重载。相反,我们可以使用classmethod。让我们创建另一个"构造函数"。

1
2
3
4
5
6
7
    @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')

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

我们在一个地方实现了日期字符串解析,现在可以重用了。封装在这里工作得很好(如果您认为可以在其他地方将字符串解析实现为单个函数,那么这个解决方案更适合OOP范例)。cls是一个持有类本身的对象,而不是类的实例。这非常酷,因为如果我们继承了Date类,所有的子类都将定义from_string

静态方法

staticmethod呢?它非常类似于classmethod,但不接受任何必需的参数(像类方法或实例方法那样)。

让我们看看下一个用例。

我们有一个想要验证的日期字符串。该任务还在逻辑上绑定到我们目前使用的Date类,但不需要实例化它。

这里是staticmethod可以使用的地方。让我们看看下一段代码:

1
2
3
4
5
6
7
    @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的另一个原因。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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类的实例。

但是,如果您仔细观察,无论如何,工厂进程都是硬编码来创建Date对象的。这意味着即使Date类是子类化的,子类仍然会创建普通的Date对象(没有子类的任何属性)。请看下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
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装饰器。

在大多数情况下,这是不需要的。如果您想要的是一个知道调用它的类的工厂方法,那么@classmethod就是您所需要的。

Date.millenium重写为(这是上面代码中惟一更改的部分)

1
2
3
@classmethod
def millenium(cls, month, day):
    return cls(month, day, 2000)

确保class不是硬编码的,而是学会的。cls可以是任何子类。得到的object将正确地成为cls的一个实例。我们来测试一下。

1
2
3
4
5
6
7
8
9
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)

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

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


Meaning of @classmethod and @staticmethod?

方法是对象名称空间中的函数,可以作为属性访问。常规方法(即instance)获取实例(我们通常称它为self)作为隐式的第一个参数。类方法获取类(通常称为cls)作为隐式的第一个参数。静态方法不会获得隐式的第一个参数(类似于正则函数)。

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

您不需要任何一个装饰器。但是根据您应该最小化函数参数数量的原则(请参阅Clean Coder),这样做很有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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()

对于实例方法和类方法,不接受至少一个参数就是类型错误,但是不理解该参数的语义就是用户错误。

(定义some_function’s,例如:

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

这是可行的。)

点查找实例和类:

对实例执行点查找的顺序如下:

类名称空间中的数据描述符(类似于属性)实例__dict__中的数据类名称空间(方法)中的非数据描述符。

注意,实例上的点查找调用如下:

1
2
instance = Example()
instance.regular_instance_method

方法是可调用的属性:

1
instance.regular_instance_method()

的实例方法

参数self通过点查找隐式给出。

您必须从类的实例访问实例方法。

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

类方法

参数cls通过点查找隐式给出。

您可以通过实例或类(或子类)访问此方法。

1
2
3
4
>>> instance.a_class_method()
<class '__main__.Example'>
>>> Example.a_class_method()
<class '__main__.Example'>

静态方法

没有隐式地给出参数。这种方法的工作原理与(例如)在模块名称空间上定义的任何函数一样,只是可以查找它

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

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

与实例方法相比,它们传递给方法的信息中的每一个都有越来越多的限制。

当你不需要这些信息的时候就使用它们。

这使您的函数和方法更容易推理和单元测试。

哪个更容易推理?

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

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

1
def function(z): ...

参数较少的函数更容易推理。它们也更容易进行单元测试。

这些类似于实例、类和静态方法。记住,当我们有一个实例时,我们也有它的类,再一次,问你自己,哪个更容易推理?

1
2
3
4
5
6
7
8
9
10
11
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名称空间访问它要方便得多。

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

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

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

当子类化时,我们看到它以类方法的形式获取类信息,这是非常有用的:

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

我的建议-结论

如果不需要类或实例参数,但是函数与对象的使用相关,并且函数位于对象的名称空间中很方便,那么可以使用静态方法。

当您不需要实例信息,但可能需要类信息用于它的其他类或静态方法时,或者可能需要类信息本身作为构造函数时,请使用类方法。(您不需要硬编码类,这样就可以在这里使用子类。)


当希望根据调用方法的子类更改方法的行为时,可以使用@classmethod。记住,我们在类方法中有一个对调用类的引用。

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

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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当您想要编写一个工厂方法并且通过这个自定义属性可以附加到一个类中时,这一点非常重要。可以在继承的类中重写此属性。

这两种方法的比较如下

Table


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

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

1
2
3
4
5
6
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,您应该一个接一个地创建实例,然后将它们嵌套起来。

1
2
3
4
5
6
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

1
2
3
4
5
6
7
8
9
10
11
12
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')

测试:

1
2
3
4
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 decorator将传统方法转换为工厂方法,使用classmethods可以添加尽可能多的替代构造函数。


我是这个网站的初学者,我已经阅读了上面所有的答案,并且得到了我想要的信息。但是,我没有表决权。所以我想从StackOverflow开始,给出我所理解的答案。

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


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


@classmethod

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

1
2
3
4
5
6
7
8
9
10
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进行比较

1
2
3
4
5
6
7
8
9
10
11
12
13
class C:
    def __init__(self):
        ....

    @staticmethod
    def static_method(args):
        ....

    def normal_method(parameters):
        ....

result = C.static_method(parameters)
result = obj.normal_method(parameters)


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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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)