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


当前回答

Python带有几个内置的装饰器。三大类是:

@classmethod
@staticmethod
@property

首先,让我们注意,类的任何函数都可以用这个类的实例调用(在初始化这个类之后)。

@classmethod是一种方法,它不仅可以作为类的实例调用函数,还可以直接由类本身作为其第一个参数调用函数。

@staticmethod是一种将函数放入类的方法(因为它在逻辑上属于类),同时表示它不需要访问类(因此我们不需要在函数定义中使用self)。

让我们考虑一下以下课程:

class DecoratorTest(object):

    def __init__(self):
        pass

    def doubler(self, x):
        return x*2

    @classmethod
    def class_doubler(cls, x): # we need to use 'cls' instead of 'self'; 'cls' reference to the class instead of an instance of the class
        return x*2

    @staticmethod
    def static_doubler(x): # no need adding 'self' here; static_doubler() could be just a function not inside the class
        return x*2

让我们看看它是如何工作的:

decor = DecoratorTest()

print(decor.doubler(5))
# 10

print(decor.class_doubler(5)) # a call with an instance of a class
# 10
print(DecoratorTest.class_doubler(5)) # a direct call by the class itself
# 10

# staticmethod could be called in the same way as classmethod.
print(decor.static_doubler(5)) # as an instance of the class
# 10
print(DecoratorTest.static_doubler(5)) # or as a direct call 
# 10

这里您可以看到这些方法的一些用例。

奖金:您可以在这里阅读@property decorator

其他回答

关于staticmethod vs classmethod的另一个考虑是继承。假设你有以下课程:

class Foo(object):
    @staticmethod
    def bar():
        return "In Foo"

然后您需要在子类中重写bar():

class Foo2(Foo):
    @staticmethod
    def bar():
        return "In Foo2"

这是可行的,但请注意,现在子类(Foo2)中的bar()实现不能再利用该类特有的任何特性。例如,假设Foo2有一个名为magic()的方法,您希望在Foo2实现bar()时使用该方法:

class Foo2(Foo):
    @staticmethod
    def bar():
        return "In Foo2"
    @staticmethod
    def magic():
        return "Something useful you'd like to use in bar, but now can't" 

这里的解决方法是在bar()中调用Foo2.magic(),但随后您重复自己的操作(如果Foo2的名称发生更改,您必须记住更新bar()方法)。

对我来说,这稍微违反了开放/封闭原则,因为在Foo中做出的决定会影响您重构派生类中的公共代码的能力(即扩展的开放性较低)。如果bar()是一个类方法,我们就可以了:

class Foo(object):
    @classmethod
    def bar(cls):
        return "In Foo"

class Foo2(Foo):
    @classmethod
    def bar(cls):
        return "In Foo2 " + cls.magic()
    @classmethod
    def magic(cls):
        return "MAGIC"

print Foo2().bar()

给出:In Foo2 MAGIC

此外:历史笔记:Guido Van Rossum(Python的创建者)曾将静态方法称为“意外”:https://mail.python.org/pipermail/python-ideas/2012-May/014969.html

我们都知道静态方法有多有限。(它们基本上是一个意外——回到Python 2.2时代,当我发明新型类和描述符时,我想实现类方法,但一开始我不理解它们,意外地先实现了静态方法。然后,删除它们,只提供类方法已经太晚了。

也:https://mail.python.org/pipermail/python-ideas/2016-July/041189.html

老实说,staticmethod是一个错误——我试图做一些类似Java类方法的事情,但一旦发布,我发现真正需要的是类方法。但要摆脱静态方法为时已晚。

静态方法是一种对所调用的类或实例一无所知的方法。它只获取传递的参数,而不是隐式的第一个参数。它在Python中基本上是无用的——您可以只使用模块函数而不是静态方法。

另一方面,类方法是一种方法,它将被调用的类或被调用的实例的类作为第一个参数传递。当您希望该方法成为类的工厂时,这很有用:因为它获得了作为第一个参数调用的实际类,所以即使涉及子类,您也可以始终实例化正确的类。例如,观察类方法dict.fromkeys()在子类上调用时如何返回子类的实例:

>>> class DictSubclass(dict):
...     def __repr__(self):
...         return "DictSubclass"
... 
>>> dict.fromkeys("abc")
{'a': None, 'c': None, 'b': None}
>>> DictSubclass.fromkeys("abc")
DictSubclass
>>> 

我认为一个更好的问题是“你什么时候会使用@classmethod vs@staticmethod?”

@classmethod允许您轻松访问与类定义关联的私有成员。这是一种很好的方法来实现单实例,或者控制所创建对象实例数量的工厂类。

@staticmethod提供了边际性能增益,但我还没有看到静态方法在类内的有效使用,而静态方法不能作为类外的独立函数实现。

让我先告诉一下用@classmethod修饰的方法和@staticmethod修饰的方法之间的相似性。

相似性:它们都可以在类本身上调用,而不仅仅是类的实例。所以,在某种意义上,这两种方法都是Class的方法。

区别:类方法将接收类本身作为第一个参数,而静态方法不接收。

因此,在某种意义上,静态方法并不绑定到类本身,只是因为它可能具有相关功能而挂在那里。

>>> class Klaus:
        @classmethod
        def classmthd(*args):
            return args

        @staticmethod
        def staticmthd(*args):
            return args

# 1. Call classmethod without any arg
>>> Klaus.classmthd()  
(__main__.Klaus,)  # the class gets passed as the first argument

# 2. Call classmethod with 1 arg
>>> Klaus.classmthd('chumma')
(__main__.Klaus, 'chumma')

# 3. Call staticmethod without any arg
>>> Klaus.staticmthd()  
()

# 4. Call staticmethod with 1 arg
>>> Klaus.staticmthd('chumma')
('chumma',)

我认为提供一个纯Python版本的staticmethod和classmethod将有助于在语言级别上理解它们之间的区别(请参阅Descriptor Howto Guide)。

这两个都是非数据描述符(如果您首先熟悉描述符,那么更容易理解它们)。

class StaticMethod(object):
    "Emulate PyStaticMethod_Type() in Objects/funcobject.c"

    def __init__(self, f):
        self.f = f

    def __get__(self, obj, objtype=None):
        return self.f


class ClassMethod(object):
    "Emulate PyClassMethod_Type() in Objects/funcobject.c"
    def __init__(self, f):
        self.f = f

    def __get__(self, obj, cls=None):
        def inner(*args, **kwargs):
            if cls is None:
                cls = type(obj)
            return self.f(cls, *args, **kwargs)
        return inner