我已经了解到,可以在Python中向现有对象(即,不在类定义中)添加方法。

我明白这样做并不总是好的。但你怎么能做到这一点呢?


当前回答

杰森·普拉特发布的内容是正确的。

>>> class Test(object):
...   def a(self):
...     pass
... 
>>> def b(self):
...   pass
... 
>>> Test.b = b
>>> type(b)
<type 'function'>
>>> type(Test.a)
<type 'instancemethod'>
>>> type(Test.b)
<type 'instancemethod'>

如您所见,Python认为b()与a()没有任何不同。在Python中,所有方法都只是恰好是函数的变量。

其他回答

杰森·普拉特发布的内容是正确的。

>>> class Test(object):
...   def a(self):
...     pass
... 
>>> def b(self):
...   pass
... 
>>> Test.b = b
>>> type(b)
<type 'function'>
>>> type(Test.a)
<type 'instancemethod'>
>>> type(Test.b)
<type 'instancemethod'>

如您所见,Python认为b()与a()没有任何不同。在Python中,所有方法都只是恰好是函数的变量。

在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'

通过阅读描述符和元类编程可以找到更多信息。

前言-关于兼容性的说明:其他答案可能只在Python2-这个答案应该在Python2和3中运行得很好。如果只编写Python3,您可能会忽略显式继承自对象,否则代码应该保持不变。

向现有对象实例添加方法我已经了解到,可以在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)

方法零(0)-使用描述符方法__get__

函数上的点查找使用实例调用函数的__get__方法,将对象绑定到方法,从而创建“绑定方法”

foo.sample_method = sample_method.__get__(foo)

现在:

>>> foo.sample_method(1,2)
3

方法一-types.MethodType

首先,导入类型,我们将从中获取方法构造函数:

import types

现在我们将该方法添加到实例中。为此,我们需要类型模块中的MethodType构造函数(我们在上面导入了它)。

types.MethodType(在Python 3中)的参数签名是(function,instance):

foo.sample_method = types.MethodType(sample_method, foo)

和用法:

>>> foo.sample_method(1,2)
3

附带地,在Python 2中,签名是(函数、实例、类):

foo.sample_method = types.MethodType(sample_method, foo, Foo)

方法二:词汇绑定

首先,我们创建一个将方法绑定到实例的包装函数:

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

方法三:functools.partial

分部函数将第一个参数应用于函数(以及可选的关键字参数),然后可以用剩余的参数(以及重写关键字参数)调用。因此:

>>> from functools import partial
>>> foo.sample_method = partial(sample_method, foo)
>>> foo.sample_method(1,2)
3    

当您认为绑定方法是实例的部分函数时,这是有意义的。

作为对象属性的未绑定函数-为什么不起作用:

如果我们尝试以与将sample_method添加到类中相同的方式添加sample_methods,它将与实例绑定,并且不会将隐式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)

我们可以通过显式传递实例(或任何东西,因为此方法实际上不使用自参数变量)来使未绑定函数工作,但它与其他实例的预期签名不一致(如果我们正在对该实例进行猴子修补):

>>> foo.sample_method(foo, 1, 2)
3

结论

你现在知道有几种方法可以做到这一点,但认真地说,不要这样做。

from types import MethodType

def method(self):
   print 'hi!'


setattr( targetObj, method.__name__, MethodType(method, targetObj, type(method)) )

使用此选项,可以使用self指针

如果有什么帮助的话,我最近发布了一个名为Gorilla的Python库,以使猴子修补过程更加方便。

使用函数needle()修补名为guinepig的模块如下:

import gorilla
import guineapig
@gorilla.patch(guineapig)
def needle():
    print("awesome")

但它还处理了文档中常见问题解答中所示的更有趣的用例。

该代码在GitHub上可用。