用@staticmethod修饰的方法和用@classmethod修饰的方法有什么区别?


当前回答

我的贡献展示了@classmethod、@staticmethod和实例方法之间的区别,包括实例如何间接调用@staticmmethod。但是,与其从实例间接调用@staticmethod,不如将其私有化可能更“Python化”。这里没有演示从私有方法获取内容,但基本上是相同的概念。

#!python3

from os import system
system('cls')
# %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %

class DemoClass(object):
    # instance methods need a class instance and
    # can access the instance through 'self'
    def instance_method_1(self):
        return 'called from inside the instance_method_1()'

    def instance_method_2(self):
        # an instance outside the class indirectly calls the static_method
        return self.static_method() + ' via instance_method_2()'

    # class methods don't need a class instance, they can't access the
    # instance (self) but they have access to the class itself via 'cls'
    @classmethod
    def class_method(cls):
        return 'called from inside the class_method()'

    # static methods don't have access to 'cls' or 'self', they work like
    # regular functions but belong to the class' namespace
    @staticmethod
    def static_method():
        return 'called from inside the static_method()'
# %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %

# works even if the class hasn't been instantiated
print(DemoClass.class_method() + '\n')
''' called from inside the class_method() '''

# works even if the class hasn't been instantiated
print(DemoClass.static_method() + '\n')
''' called from inside the static_method() '''
# %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %   %

# >>>>> all methods types can be called on a class instance <<<<<
# instantiate the class
democlassObj = DemoClass()

# call instance_method_1()
print(democlassObj.instance_method_1() + '\n')
''' called from inside the instance_method_1() '''

# # indirectly call static_method through instance_method_2(), there's really no use
# for this since a @staticmethod can be called whether the class has been
# instantiated or not
print(democlassObj.instance_method_2() + '\n')
''' called from inside the static_method() via instance_method_2() '''

# call class_method()
print(democlassObj.class_method() + '\n')
'''  called from inside the class_method() '''

# call static_method()
print(democlassObj.static_method())
''' called from inside the static_method() '''

"""
# whether the class is instantiated or not, this doesn't work
print(DemoClass.instance_method_1() + '\n')
'''
TypeError: TypeError: unbound method instancemethod() must be called with
DemoClass instance as first argument (got nothing instead)
'''
"""

其他回答

要决定是使用@staticmethod还是@classmethod,必须查看方法内部。如果您的方法访问类中的其他变量/方法,请使用@classmethod。另一方面,如果您的方法不涉及类的任何其他部分,则使用@staticmethod。

class Apple:

    _counter = 0

    @staticmethod
    def about_apple():
        print('Apple is good for you.')

        # note you can still access other member of the class
        # but you have to use the class instance 
        # which is not very nice, because you have repeat yourself
        # 
        # For example:
        # @staticmethod
        #    print('Number of apples have been juiced: %s' % Apple._counter)
        #
        # @classmethod
        #    print('Number of apples have been juiced: %s' % cls._counter)
        #
        #    @classmethod is especially useful when you move your function to another class,
        #       you don't have to rename the referenced class 

    @classmethod
    def make_apple_juice(cls, number_of_apples):
        print('Making juice:')
        for i in range(number_of_apples):
            cls._juice_this(i)

    @classmethod
    def _juice_this(cls, apple):
        print('Juicing apple %d...' % apple)
        cls._counter += 1

python官方文档:

@分类法

类方法将类作为隐式第一个参数,就像instance方法接收该实例。要声明类方法,请使用成语:C类:@分类法定义f(cls,arg1,arg2,…):。。。@classmethod表单是一个函数decorator–请参见函数中的函数定义详细定义。它可以在类上调用(例如C.f())或实例(例如C().f())。实例是除了它的类之外,都被忽略。如果类方法用于派生类,派生类对象是作为隐含的第一个参数传递。类方法不同于C++或Java静态方法。如果你愿意这些,请参见本文中的staticmethod()部分

@静态方法

静态方法不接收隐式第一个参数。声明静态方法,使用这个成语:C类:@静态方法定义f(arg1,arg2,…):。。。@staticmethod表单是一个函数decorator–请参见函数中的函数定义详细定义。它可以在类上调用(例如C.f())或实例(例如C().f())。实例是除了它的类之外,都被忽略。Python中的静态方法类似类似于Java或C++中的那些。对于更高级的概念,请参见classmethod()。

@staticmethod只是禁用默认函数作为方法描述符。classmethod将函数包装在可调用的容器中,该容器将引用作为第一个参数传递给所属类:

>>> class C(object):
...  pass
... 
>>> def f():
...  pass
... 
>>> staticmethod(f).__get__(None, C)
<function f at 0x5c1cf0>
>>> classmethod(f).__get__(None, C)
<bound method type.f of <class '__main__.C'>>

事实上,classmethod有运行时开销,但可以访问所属的类。或者,我建议使用元类并将类方法放在元类上:

>>> class CMeta(type):
...  def foo(cls):
...   print cls
... 
>>> class C(object):
...  __metaclass__ = CMeta
... 
>>> C.foo()
<class '__main__.C'>

也许一些示例代码会有所帮助:注意foo、class_foo和static_foo的调用签名的不同:

class A(object):
    def foo(self, x):
        print(f"executing foo({self}, {x})")

    @classmethod
    def class_foo(cls, x):
        print(f"executing class_foo({cls}, {x})")

    @staticmethod
    def static_foo(x):
        print(f"executing static_foo({x})")

a = A()

下面是对象实例调用方法的常用方法。对象实例a作为第一个参数隐式传递。

a.foo(1)
# executing foo(<__main__.A object at 0xb7dbef0c>, 1)

使用classmethods,对象实例的类作为第一个参数而不是self隐式传递。

a.class_foo(1)
# executing class_foo(<class '__main__.A'>, 1)

也可以使用类调用class_foo。事实上,如果你定义了类方法,这可能是因为您打算从类而不是从类实例调用它。A.foo(1)会引发TypeError,但A.class_foo(1)工作正常:

A.class_foo(1)
# executing class_foo(<class '__main__.A'>, 1)

人们发现类方法的一个用途是创建可继承的替代构造函数。


使用staticmethods,self(对象实例)和cls(类)都不会作为第一个参数隐式传递。它们的行为类似于普通函数,只是您可以从实例或类调用它们:

a.static_foo(1)
# executing static_foo(1)

A.static_foo('hi')
# executing static_foo(hi)

静态方法用于将与类有某种逻辑联系的函数分组到该类。


foo只是一个函数,但当你调用.foo时,你不只是得到函数,您将得到函数的“部分应用”版本,其中对象实例a作为函数的第一个参数。foo需要2个参数,而a.foo只需要1个参数。

a绑定到foo。这就是以下术语“约束”的含义:

print(a.foo)
# <bound method A.foo of <__main__.A object at 0xb7d52f0c>>

对于.class_foo,a不绑定到class_foo,而类a绑定到class-foo。

print(a.class_foo)
# <bound method type.class_foo of <class '__main__.A'>>

这里,对于staticmethod,即使它是一个方法,a.static_foo也只返回一个没有参数约束的好的ole函数。static_foo需要1个参数,并且.static_foo也需要1个参数。

print(a.static_foo)
# <function static_foo at 0xb7d479cc>

当然,当用类A调用static_foo时也会发生同样的情况。

print(A.static_foo)
# <function static_foo at 0xb7d479cc>

对iPython中其他相同方法的快速破解表明,@staticmethod产生了边际性能增益(以纳秒为单位),但在其他方面它似乎没有任何作用。此外,在编译过程中通过staticmethod()处理方法的额外工作(这在运行脚本时任何代码执行之前发生)可能会抵消任何性能提升。

为了代码的可读性,我会避免@staticmethod,除非您的方法将用于纳秒计数的工作量。