24

采用以下示例脚本:

class A(object):
    @classmethod
    def one(cls):
        print("I am class")

    @staticmethod
    def two():
        print("I am static")


class B(object):
    one = A.one
    two = A.two


B.one()
B.two()

当我使用 Python 2.7.11 运行此脚本时,我得到:

I am class
Traceback (most recent call last):
  File "test.py", line 17, in <module>
    B.two()
TypeError: unbound method two() must be called with B instance as first argument (got nothing instead)

似乎 @classmethod 装饰器在类中保留,但 @staticmethod 不是。

Python 3.4 的行为符合预期:

I am class
I am static

为什么 Python2 不保留 @staticmethod,是否有解决方法?

编辑:从班级中抽出两个(并保留@staticmethod)似乎可行,但这对我来说仍然很奇怪。

4

2 回答 2

15

classmethod并且staticmethod是描述符,它们都没有按照您的预期做,而不仅仅是staticmethod.

当您访问A.one时,它会在 上创建一个绑定方法A,然后将其设为 的属性B,但由于它绑定到A,因此即使您调用,cls参数也将始终为(在 Python 2 和 Python 3 上都是这种情况;到处都是错误的)。AB.one

当您访问A.two时,它会返回原始函数对象(staticmethod描述符不需要做任何特殊的事情,除了防止通过self或的绑定cls,所以它只返回它包装的内容)。但是该原始函数对象随后被附加B为未绑定的实例方法,因为没有staticmethod包装,就像您通常定义它一样。

后者在 Python 3 中起作用的原因是 Python 3 没有未绑定方法的概念。它具有函数(如果通过类的实例访问,则成为绑定方法)和绑定方法,其中 Python 2 具有函数、未绑定方法和绑定方法。

未绑定的方法检查是否使用正确类型的对象调用它们,从而检查您的错误。普通函数只需要正确数量的参数。

staticmethodPython 3 中的装饰器仍然返回原始函数对象,但在 Python 3 中,这很好;因为它不是一个特殊的未绑定方法对象,如果你在类本身上调用它,它就像一个命名空间函数,而不是任何类型的方法。如果您尝试这样做,您会看到问题:

B().two()

但是,因为这将在该实例B和函数之外创建一个绑定方法,传递一个不接受two的额外参数 ( self) 。two基本上,在 Python 3 上,staticmethod让您在不引起绑定的情况下调用实例上的函数很方便,但是如果您只通过引用类本身来调用函数,则不需要它,因为它只是一个普通函数,而不是 Python 2 “未绑定方法”。

如果您有某些理由执行此副本(通常,我建议从 继承A,但无论如何),并且您想确保获得函数的描述符包装版本,而不是在访问时描述符为您提供的任何内容A,您' 会通过直接访问A'来绕过描述符协议__dict__

class B(object):
    one = A.__dict__['one']
    two = A.__dict__['two']

通过直接从类字典中复制,描述符协议魔法永远不会被调用,你会得到 and 的和staticmethod包装classmethod版本。onetwo

于 2016-12-02T17:25:38.423 回答
4

免责声明:这不是真正的答案,但也不适合评论格式。

请注意,Python2@classmethod也不能跨类正确保留。在下面的代码中,调用B.one()就像是通过 class 调用一样A

$ cat test.py 
class A(object):
    @classmethod
    def one(cls):
        print("I am class", cls.__name__)

class A2(A):
    pass

class B(object):
    one = A.one


A.one()
A2.one()
B.one()

$ python2 test.py 
('I am class', 'A')
('I am class', 'A2')
('I am class', 'A')
于 2016-12-02T17:08:55.633 回答