一、直观解释:
当您将一个函数分配给一个类时,它会被转换为一个未绑定的方法。
未绑定方法是一种神奇的东西,它为类的每个实例创建一个绑定方法。
绑定方法是一种神奇的东西,self
它绑定了特殊的参数。
(这在 3.x 中略有不同,因为没有未绑定的方法;函数本身就是为类的每个实例创建绑定方法的神奇事物。但您显然是在询问 2.x。)
所以:
>>> class C(object): pass
>>> def foo(self): print(self)
>>> C.foo = foo
>>> c = C()
现在你可以调用c.foo()
了,self
参数会自动填充为c
。但是你不能在C.foo()
没有参数的情况下调用,就像你不能foo()
在没有参数的情况下调用一样。
>>> c.foo()
<__main__.C object at 0x108df7e50>
>>> C.foo()
TypeError: unbound method foo() must be called with C instance as first argument (got nothing instead)
>>> foo()
TypeError: foo() takes exactly 1 argument (0 given)
当然,没有什么能阻止你打电话C.foo()
或foo()
使用明确的self
论据。你通常不会这样做,但它工作正常:
>>> C.foo(1)
1
>>> foo(1)
1
如果您查看函数、未绑定方法和绑定方法,它们很容易区分:
>>> foo, C.foo, c.foo
(<function __main__.foo>, <unbound method C.foo>, <bound method C.foo of <__main__.C object at 0x108df7e50>>)
>>> type(foo), type(C.foo), type(c.foo)
(function, instancemethod, instancemethod)
如果您只是想要一种解决方法,您可以使用staticmethod
. 这需要一个函数并创建一个东西,当它分配给一个类时,它就像一个函数而不是一个未绑定的方法:
>>> bar = staticmethod(foo)
>>> C.bar = staticmethod(foo)
>>> c = C()
>>> bar, C.bar, c.bar
(<staticmethod at 0x109e990f8>, <function __main__.foo>, <function __main__.foo>)
现在,您可以在类或实例上将其作为常规函数调用,而无需使用魔法self
方法:
>>> C.bar(1)
1
>>> c.bar(1)
1
>>> bar(1)
TypeError: 'staticmethod' object is not callable
但这种直观的解释并不是真的。它总是会引导你找到正确的答案,但这并不是事情的真正运作方式。
要真正理解这一点,您需要了解描述符的工作原理。
当您将函数作为属性分配给类时,不会发生任何神奇的事情。类字典中存储的只是普通的旧函数:
>>> C.__dict__['foo']
<function __main__.foo>
当你创建一个实例时,它不会在实例字典中放任何东西,因为属性查找会自动回退到类字典:
>>> c.__dict__['foo']
KeyError: 'foo'
真正的魔法发生在查找时。每当您查找任何属性(方法或其他)时,解释器不仅会从字典中返回对象,它还会调用该对象的__get__
方法,传递实例(或者None
,如果在类对象上调用)和类。
普通函数具有__get__
根据需要生成绑定或未绑定方法的方法:
>>> C.__dict__['foo'].__get__(None, C)
<unbound method C.foo>
>>> C.__dict__['foo'].__get__(None, C) == C.foo
True
>>> C.__dict__['foo'].__get__(c, C)
<bound method C.foo of <__main__.C object at 0x108df7e50>>
>>> C.__dict__['foo'].__get__(c, C) == c.foo
True
您可以手动执行相同的操作并获得相同的结果:
>>> types.MethodType(foo, None, C) == C.foo
True
>>> types.MethodType(foo, c, C) == c.foo
True
现在你大概可以猜到是什么staticmethod
样子了:它有一个__get__
方法只返回原始函数,不管你传递什么。
处理可设置的数据属性等有点复杂,但实际上,这几乎就是全部,如果你理解了这一点,你就几乎理解了 Python 中的所有魔力。您可以自己构建staticmethod
、classmethod
和property
,从头开始构建类等。