在 Python 中,函数和绑定方法之间存在差异。
>>> def foo():
... print "foo"
...
>>> class A:
... def bar( self ):
... print "bar"
...
>>> a = A()
>>> foo
<function foo at 0x00A98D70>
>>> a.bar
<bound method A.bar of <__main__.A instance at 0x00A9BC88>>
>>>
绑定方法已 “绑定”(描述性)到一个实例,并且无论何时调用该方法,该实例都将作为第一个参数传递。
但是,作为类(而不是实例)的属性的可调用对象仍未绑定,因此您可以在需要时随时修改类定义:
>>> def fooFighters( self ):
... print "fooFighters"
...
>>> A.fooFighters = fooFighters
>>> a2 = A()
>>> a2.fooFighters
<bound method A.fooFighters of <__main__.A instance at 0x00A9BEB8>>
>>> a2.fooFighters()
fooFighters
先前定义的实例也会被更新(只要它们本身没有覆盖属性):
>>> a.fooFighters()
fooFighters
当您要将方法附加到单个实例时,就会出现问题:
>>> def barFighters( self ):
... print "barFighters"
...
>>> a.barFighters = barFighters
>>> a.barFighters()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: barFighters() takes exactly 1 argument (0 given)
将函数直接附加到实例时,该函数不会自动绑定:
>>> a.barFighters
<function barFighters at 0x00A98EF0>
要绑定它,我们可以在类型模块中使用 MethodType 函数:
>>> import types
>>> a.barFighters = types.MethodType( barFighters, a )
>>> a.barFighters
<bound method ?.barFighters of <__main__.A instance at 0x00A9BC88>>
>>> a.barFighters()
barFighters
这次,该类的其他实例没有受到影响:
>>> a2.barFighters()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: A instance has no attribute 'barFighters'
前言 - 关于兼容性的说明:其他答案可能仅在 Python 2 中有效 - 此答案在 Python 2 和 3 中应该可以很好地工作。如果仅编写 Python 3,则可能会显式地从object
继承,否则代码应保留在相同的。
向现有对象实例添加方法
我读过,可以在 Python 中向现有对象(例如不在类定义中)添加方法。
我了解这样做并非总是一个好的决定。但是,怎么可能呢?
我不建议这样做。这是一个坏主意。不要这样
这有两个原因:
因此,除非您有充分的理由,否则我建议您不要这样做。这是更好的在类定义来定义的正确方法或类直接不太优选到猴的贴剂,是这样的:
Foo.sample_method = sample_method
由于具有指导意义,因此,我将向您展示一些实现此目的的方法。
这是一些设置代码。我们需要一个类定义。可以将其导入,但这并不重要。
class Foo(object):
'''An empty class to demonstrate adding a method to an instance'''
创建一个实例:
foo = Foo()
创建一个要添加到其中的方法:
def sample_method(self, bar, baz):
print(bar + baz)
__get__
在函数上进行点分查询会将该函数的__get__
方法与实例一起调用,将对象绑定到该方法,从而创建一个 “绑定方法”。
foo.sample_method = sample_method.__get__(foo)
现在:
>>> foo.sample_method(1,2)
3
首先,导入类型,从中我们将获得方法构造函数:
import types
现在我们将方法添加到实例中。为此,我们需要types
模块中的 MethodType 构造函数(已在上面导入)。
types.MethodType 的参数签名为(function, instance, class)
:
foo.sample_method = types.MethodType(sample_method, foo, Foo)
和用法:
>>> foo.sample_method(1,2)
3
首先,我们创建一个包装器函数,将方法绑定到实例:
def bind(instance, method):
def binding_scope_fn(*args, **kwargs):
return method(instance, *args, **kwargs)
return binding_scope_fn
用法:
>>> foo.sample_method = bind(foo, sample_method)
>>> foo.sample_method(1,2)
3
局部函数将第一个自变量应用于函数(以及可选的关键字自变量),以后可以与其余自变量(和覆盖的关键字自变量)一起调用。因此:
>>> from functools import partial
>>> foo.sample_method = partial(sample_method, foo)
>>> foo.sample_method(1,2)
3
当您认为绑定方法是实例的部分功能时,这很有意义。
如果尝试以与将其添加到类中相同的方式添加 sample_method,则它不受实例约束,并且不会将隐式 self 作为第一个参数。
>>> foo.sample_method = sample_method
>>> foo.sample_method(1,2)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: sample_method() takes exactly 3 arguments (2 given)
我们可以通过显式传递实例(或其他任何方法,因为此方法实际上并不使用self
参数变量)来使未绑定函数起作用,但是它与其他实例的预期签名不一致(如果我们是猴子,修补此实例):
>>> foo.sample_method(foo, 1, 2)
3
现在,您知道可以执行此操作的几种方法,但是很认真地讲 - 不要这样做。
自 python 2.6 起不推荐使用 new模块,并在 3.0 版中将其删除,请使用类型
参见http://docs.python.org/library/new.html
在下面的示例中,我故意从patch_me()
函数中删除了返回值。我认为提供返回值可能会使人相信 patch 返回了一个新对象,这是不正确的 - 它修改了传入的对象。可能这可以促进对猴子补丁的更严格的使用。
import types
class A(object):#but seems to work for old style objects too
pass
def patch_me(target):
def method(target,x):
print "x=",x
print "called from", target
target.method = types.MethodType(method,target)
#add more if needed
a = A()
print a
#out: <__main__.A object at 0x2b73ac88bfd0>
patch_me(a) #patch instance
a.method(5)
#out: x= 5
#out: called from <__main__.A object at 0x2b73ac88bfd0>
patch_me(A)
A.method(6) #can patch class too
#out: x= 6
#out: called from <class '__main__.A'>