Python:将静态方法分配给 class 变量会出错

Python: Assigning staticmethod to class variable gives error

我想将静态方法分配给 Python 中的 class 变量,下面是我的代码。

class Klass:
    classVariable = None

    @staticmethod
    def method():
        print "method called"        

Klass.classVariable = Klass.method        
Klass.method()
Klass.classVariable()

这在最后一行给了我一个错误, TypeError: unbound method method() must be called with Klass instance as first argument (got nothing instead).

但是当我将静态方法更改为 class 方法时它起作用了。谁能告诉我为什么会这样?

背景故事(描述符协议)

首先,我们需要了解一下python descriptors...

对于这个答案,知道以下内容应该就足够了:

  1. 函数是描述符。
  2. 方法的绑定行为(即方法如何知道 self 传递什么)是通过函数的 __get__ 方法和内置描述符协议实现的。
  3. 当你把描述符foo放在class上时,访问描述符实际上调用了.__get__方法。(这真的只是陈述 2)
  4. 的概括

换句话说:

class Foo(object):
    val = some_descriptor

当我这样做时:

result = Foo.val

Python 实际上是:

Foo.val.__get__(None, Foo)

当我这样做时:

f = Foo()
f.val

python 是:

f = Foo()
type(f).val.__get__(f, type(f))

现在是好东西。

看起来(在 python2.x 上),staticmethod 的实现使得它的 __get__ 方法 return 是一个常规函数。您可以通过打印 Klass.method:

的类型来查看
print type(Klass.method)  # <type 'function'>

所以我们了解到 Klass.method.__get__ 编辑的方法 return 只是一个常规函数。

当您将该常规函数放到 class 上时,它是 __get__ 方法 return 和 instancemethod(需要 self 参数)。这并不奇怪......我们一直这样做:

class Foo(object):
    def bar(self):
        print self

与 python 没有区别:

def bar(self):
    print self

class Foo(object):
    pass

Foo.bar = bar

除了第一个版本更容易阅读并且不会弄乱您的模块命名空间。

现在我们已经解释了您的静态方法是如何变成实例方法的。我们能做些什么吗?

解决方案

当你把方法放到 class 上时,再次将它指定为 staticmethod 就可以了。

class Klass(object):  # inheriting from object is a good idea.
    classVariable = None

    @staticmethod
    def method():
        print("method called")

Klass.classVariable = staticmethod(Klass.method)  # Note extra staticmethod
Klass.method()
Klass.classVariable()

附录 -- @staticmethod

的重新实现

如果您有点好奇如何将 staticmethod 实施到 则不会遇到这个问题 -- 这是一个示例:

class StaticMethod(object):
    def __init__(self, fn):
        self.fn = fn

    def __get__(self, inst, cls):
        return self

    def __call__(self, *args, **kwargs):
        return self.fn(*args, **kwargs)


class Klass(object):
    classVariable = None

    @StaticMethod
    def method():
        print("method called")

Klass.classVariable = Klass.method
Klass.method()
Klass.classVariable()
Klass().method()
Klass().classVariable()

这里的技巧是我的 __get__ 没有 return 函数。它 return 是 本身 。当你把它放在不同的 class(或相同的 class)上时,它的 __get__ 仍然只是 return 本身。因为它是从 __get__ 自身 return,它需要伪装成一个函数(所以它可以在“__gotten__”之后调用)所以我实现了一个自定义 __call__方法做正确的事情(传递给委托函数和 return 结果)。

请注意,我并不是提倡您使用此 StaticMethod 而不是 staticmethod。它的效率会降低并且不会自省(并且可能会让您的代码读者感到困惑)。这仅用于教育目的。