为什么 @staticmethod 没有在 类 中保留,而 @classmethod 是?

Why is @staticmethod not preserved across classes, when @classmethod is?

采用以下示例脚本:

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 装饰器似乎在 classes 中得到了保留,但 @staticmethod 却没有。

Python 3.4 表现符合预期:

I am class
I am static

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

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

DISCLAIMER: This is not really an answer, but it doesn't fit into a comment format either.

请注意 Python2 @classmethod 也不是 正确地 在 class 中保留。在下面的代码中,对 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')

classmethodstaticmethod 是描述符,它们都没有按照您的预期进行,而不仅仅是 staticmethod.

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

当您访问 A.two 时,它返回原始函数对象(staticmethod 描述符不需要做任何特殊的事情,除了防止绑定传递 selfcls,所以它只是 returns 它包装的内容)。但是那个原始函数对象然后作为未绑定的实例方法附加到 B,因为没有 staticmethod 包装,它就像你正常定义它一样。

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

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

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

B().two()

不过,因为这会从 Btwo 函数的实例中创建一个绑定方法,传递一个额外的参数 (self) two不接受。基本上,在 Python 3 上,staticmethod 可以方便地让您在实例上调用函数而不会导致绑定,但是如果您只通过引用 class 本身来调用函数,则不是需要,因为它只是一个普通函数,而不是 Python 2 "unbound method".

如果您出于某种原因执行此复制(通常,我建议从 A 继承,但无论如何),并且您希望确保获得函数的描述符包装版本,而不是无论在 A 上访问时描述符给你什么,你都可以通过直接访问 A__dict__:

来绕过描述符协议
class B(object):
    one = A.__dict__['one']
    two = A.__dict__['two']

通过直接从 class 字典中复制,永远不会调用描述符协议魔法,你会得到 onestaticmethodclassmethod 包装版本=30=].