Python:将staticmethod分配给类变量会产生错误

hit*_*han 3 python static-methods class-method

我想在Python中为一个类变量赋一个静态方法,下面就是我的代码.

class Klass:
    classVariable = None

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

Klass.classVariable = Klass.method        
Klass.method()
Klass.classVariable()
Run Code Online (Sandbox Code Playgroud)

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

但是当我将静态方法更改为类方法时,它可以工作.任何人都可以告诉我为什么会这样吗?

mgi*_*son 6

背景故事(描述符协议)

首先,我们需要了解一些关于python 描述符 ...

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

  1. 函数是描述符.
  2. 方法的绑定行为(即方法如何知道self要传递的内容)是通过函数的__get__方法和内置的描述符协议实现的.
  3. 当您foo在类上放置描述符时,访问描述符实际上会调用该.__get__方法. (这实际上只是声明2的概括)

换一种说法:

class Foo(object):
    val = some_descriptor
Run Code Online (Sandbox Code Playgroud)

当我做:

result = Foo.val
Run Code Online (Sandbox Code Playgroud)

Python实际上做了:

Foo.val.__get__(None, Foo)
Run Code Online (Sandbox Code Playgroud)

当我做:

f = Foo()
f.val
Run Code Online (Sandbox Code Playgroud)

python做:

f = Foo()
type(f).val.__get__(f, type(f))
Run Code Online (Sandbox Code Playgroud)

现在好东西.

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

print type(Klass.method)  # <type 'function'>
Run Code Online (Sandbox Code Playgroud)

所以我们学到的是返回的方法Klass.method.__get__只是一个常规函数.

当你将那个常规函数放到一个类上时,它的__get__方法返回一个instancemethod(它需要一个self参数).这并不奇怪...我们一直这样做:

class Foo(object):
    def bar(self):
        print self
Run Code Online (Sandbox Code Playgroud)

与python没有什么不同:

def bar(self):
    print self

class Foo(object):
    pass

Foo.bar = bar
Run Code Online (Sandbox Code Playgroud)

除了第一个版本更容易阅读并且不会使您的模块名称空间混乱.

所以现在我们已经解释了你的staticmethod如何变成一个实例方法.我们能做些什么吗?

解

当你把方法放到类上时,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()
Run Code Online (Sandbox Code Playgroud)

附录 - 重新实施 @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()
Run Code Online (Sandbox Code Playgroud)

这里的诀窍是我__get__没有返回一个函数.它返回自己.当你把它放在一个不同的类(或同一个类)时,它__get__仍将只返回自己.因为它自己返回__get__,所以它需要伪装成一个函数(所以它可以在"__gotten__"之后调用)所以我实现了一个自定义__call__方法来做正确的事情(传递给委托函数并返回结果).

请注意,我并不是主张你用它StaticMethod来代替staticmethod.它的效率会降低,而且不会过于苛刻(并且可能会让您的代码读者感到困惑).这仅用于教育目的.