当@classmethod是什么时,为什么@staticmethod不会跨类保留?

Ale*_*bes 23 python static-methods class-method python-2.7 python-descriptors

采用以下示例脚本:

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()
Run Code Online (Sandbox Code Playgroud)

当我使用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)
Run Code Online (Sandbox Code Playgroud)

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

Python 3.4的行为符合预期:

I am class
I am static
Run Code Online (Sandbox Code Playgroud)

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

编辑:从课堂上取两个(并保留@staticmethod)似乎有效,但这对我来说似乎仍然很奇怪.

Sha*_*ger 15

classmethod并且staticmethod是描述符,并且他们都没有做你期望的事情,而不仅仅是staticmethod.

当你访问时A.one,它正在创建一个绑定方法A,然后使它成为一个属性B,但因为它被绑定A,cls参数将始终是A,即使你调用B.one(这是Python 2和Python 3的情况;它在各处都是错的).

当您访问A.two,它返回原始函数对象(staticmethod描述并不需要从预防的结合,将通过做什么特别的一边self或者cls,所以它只是返回它所包裹).但是那个原始函数对象然后被附加到B一个未绑定的实例方法,因为没有staticmethod包装,它就像你正常定义它一样.

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

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

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

B().two()
Run Code Online (Sandbox Code Playgroud)

但是,因为那将从该实例Btwo函数中生成一个绑定方法,传递一个不接受的额外参数(self)two.基本上,在Python 3上,staticmethod让你在不引起绑定的情况下调用实例上的函数是一种方便,但是如果你只是通过引用类本身来调用函数,则不需要它,因为它只是一个简单的函数,而不是Python 2 "无约束的方法".

如果你有理由执行这个副本(通常,我建议继承A,但无论如何),并且你想确保获得函数的描述符包装版本,而不是描述符在访问时给你的任何内容A,你通过直接访问A's来绕过描述符协议__dict__:

class B(object):
    one = A.__dict__['one']
    two = A.__dict__['two']
Run Code Online (Sandbox Code Playgroud)

通过从类字典直接复制,描述协议魔法永远不会调用,你得到的staticmethodclassmethod包裹版本onetwo.


Leo*_*eon 5

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

请注意,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')
Run Code Online (Sandbox Code Playgroud)