在Python中为现有对象实例添加方法

855 投票
19 回答
385254 浏览
提问于 2025-04-11 00:09

我该如何在Python中给一个已经存在的对象添加一个方法(也就是说,不是在类的定义里)呢?

我明白这样做通常不被认为是好习惯,除非在某些特定情况下。

19 个回答

105

模块 new 从 Python 2.6 开始就不推荐使用了,在 3.0 版本中被移除了,建议使用 types 模块。

详情请查看 http://docs.python.org/library/new.html

在下面的例子中,我故意去掉了 patch_me() 函数的返回值。我认为如果给出返回值,可能会让人误以为 patch 返回了一个新对象,但实际上并不是这样——它是修改了传入的对象。这样做可能有助于更规范地使用猴子补丁(monkeypatching)。

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

前言 - 关于兼容性的说明:其他答案可能只适用于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)

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

在函数上使用点查找会调用该函数的__get__方法,将对象绑定到方法上,从而创建一个“绑定方法”。

foo.sample_method = sample_method.__get__(foo)

现在:

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

方法一 - types.MethodType

首先,导入types模块,从中获取方法构造函数:

import types

现在我们将方法添加到实例中。为此,我们需要从types模块(我们刚才导入的)中获取MethodType构造函数。

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

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

使用示例:

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

顺便说一下,在Python 2中,签名是(function, instance, class)

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,它是未绑定的,不会将隐式的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

结论

你现在知道了几种你可以这样做的方法,但认真说 - 不要这样做。

1171

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

要绑定它,我们可以使用 types模块中的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'

想了解更多信息,可以阅读关于 描述符类的元编程 的内容。

撰写回答