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


当前回答

关于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类方法的事情,但一旦发布,我发现真正需要的是类方法。但要摆脱静态方法为时已晚。

其他回答

@类方法:

可以通过cls和直接通过类名调用类变量和实例、类和静态方法,但不能通过实例变量。可以通过对象和类名直接调用。第一个参数需要cls,否则无法调用@classmethod,并且按照惯例使用cls的名称,因此其他名称而不是cls仍然有效。

@静态方法:

既可以由对象调用,也可以直接由类名调用。可以通过类名而不是实例变量直接调用类变量和实例、类和静态方法。不需要self或cls。

*在回答Python中什么是“实例方法”时,我还详细解释了实例方法?

@类方法:

例如,@classmethod可以通过cls和直接通过类名调用类变量和实例、类和静态方法,@classmethod可以通过对象和直接通过类名称调用,如下所示:

class Person:
    x = "Hello"
    def __init__(self, name):
        self.name = name
    
    @classmethod # Here
    def test1(cls):
        print(cls.x)   # Class variable by `cls`
        cls.test2(cls) # Instance method by `cls`
        cls.test3()    # Class method by `cls`
        cls.test4()    # Static method by `cls`
        print()
        print(Person.x)       # Class variable by class name
        Person.test2("Test2") # Instance method by class name
        Person.test3()        # Class method by class name
        Person.test4()        # Static method by class name
    
    def test2(self):
        print("Test2")
        
    @classmethod
    def test3(cls):
        print("Test3")
        
    @staticmethod
    def test4():
        print("Test4")

obj = Person("John")
obj.test1() # By object

# Or

Person.test1() # By class name

输出:

Hello
Test2
Test3
Test4

Hello
Test2
Test3
Test4

而且,@classmethod不能同时通过cls和直接通过类名调用实例变量,因此如果@classmethod试图同时通过cls和直接通过类名称调用实例变量(如下所示):

# ...
    
    @classmethod
    def test1(cls):
        print(cls.name) # Instance variable by `cls`
        
        # Or

        print(Person.name) # Instance variable by class name
# ...

obj = Person("John")
obj.test1()

# Or

Person.test1()

出现以下错误:

AttributeError:类型对象“Person”没有属性“name”

如果@classmethod没有cls:

# ...
    
    @classmethod
    def test1(): # Without "cls"
        print("Test1")
  
# ...

obj = Person("John")
obj.test1()

# Or

Person.test1()

@无法调用classmethod,则出现如下错误:

TypeError:test1()采用0个位置参数,但给出了1个

而且,cls的名称在约定中使用,因此其他名称而不是cls仍然有效,如下所示:

# ...

    @classmethod
    def test1(orange):
        print(orange.x)      # Class variable
        orange.test2(orange) # Instance method
        orange.test3()       # Class method
        orange.test4()       # Static method

# ...

obj = Person("John")
obj.test1()

# Or

Person.test1()

输出:

Hello
Test2
Test3
Test4

@静态方法:

例如,@staticmethod既可以按对象调用,也可以按类名直接调用,如下所示:

class Person:
    x = "Hello"
    def __init__(self, name):
        self.name = name

    @staticmethod # Here
    def test1():
        print("Test1")
    
    def test2(self):
        print("Test2")
        
    @classmethod
    def test3(cls):
        print("Test3")
        
    @staticmethod
    def test4():
        print("Test4")

obj = Person("John")
obj.test1() # By object

# Or

Person.test1() # By class name

输出:

Test1

而且,@staticmethod可以通过类名直接调用类变量和实例、类和静态方法,而不是实例变量,如下所示:

# ...
    
    @staticmethod
    def test1():
        print(Person.x)       # Class variable
        Person.test2("Test2") # Instance method
        Person.test3()        # Class method
        Person.test4()        # Static method
            
# ...

obj = Person("John")
obj.test1()

# Or

Person.test1()

输出:

Hello
Test2
Test3
Test4

并且,如果@staticmethod尝试调用实例变量,如下所示:

# ...
    
    @staticmethod
    def test1():
        print(Person.name) # Instance variable
            
# ...

obj = Person("John")
obj.test1()

# Or

Person.test1()

出现以下错误:

AttributeError:类型对象“Person”没有属性“name”

而且,@staticmethod不需要self或cls,因此如果@staticmmethod具有self或cl,则需要传递如下所示的参数:

# ...
    
    @staticmethod
    def test1(self): # With "self"
        print(self)

    # Or

    @staticmethod
    def test1(cls): # With "cls"
        print(cls)

# ...

obj = Person("John")
obj.test1("Test1") # With an argument

# Or

Person.test1("Test1") # With an argument

输出:

Test1

否则,如果不传递如下所示的参数:

# ...
    
    @staticmethod
    def test1(self): # With "self"
        print("Test1")

    # Or

    @staticmethod
    def test1(cls): # With "cls"
        print("Test1")

# ...

obj = Person("John")
obj.test1() # Without an argument

# Or

Person.test1() # Without an argument

出现以下错误:

TypeError:test1()缺少1个必需的位置参数:“self”

TypeError:test1()缺少1个必需的位置参数:“cls”

让我先告诉一下用@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',)

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

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

我认为提供一个纯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

@python2.4中添加了修饰符。如果您使用的是python<2.4,则可以使用classmethod()和staticmethod()函数。

例如,如果您想要创建一个工厂方法(一个函数,它返回一个类的不同实现的实例,具体取决于它获得的参数),您可以执行以下操作:

class Cluster(object):

    def _is_cluster_for(cls, name):
        """
        see if this class is the cluster with this name
        this is a classmethod
        """ 
        return cls.__name__ == name
    _is_cluster_for = classmethod(_is_cluster_for)

    #static method
    def getCluster(name):
        """
        static factory method, should be in Cluster class
        returns a cluster object for the given name
        """
        for cls in Cluster.__subclasses__():
            if cls._is_cluster_for(name):
                return cls()
    getCluster = staticmethod(getCluster)

还要注意,这是一个使用类方法和静态方法的好例子,静态方法显然属于类,因为它在内部使用了类Cluster。类方法只需要关于类的信息,而不需要对象的实例。

将_is_cluster_for方法设置为类方法的另一个好处是,子类可以决定更改其实现,这可能是因为它非常通用,可以处理多种类型的集群,因此仅检查类的名称是不够的。