向现有对象实例添加方法

python oop methods monkeypatching


我读到,在Python中,可以在一个现有的对象中添加一个方法(即不在类定义中)。

我明白,这样做不一定好。但如何才能做到这一点呢?




Answer 1 Jason Pratt


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

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




Answer 2 Evgeny


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



Answer 3 Aaron Hall


前言-有关兼容性的说明:其他答案可能仅在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

首先,导入类型,我们将从中得到方法构造函数。

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

usage:

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

Conclusion

现在,您知道可以执行此操作的几种方法,但是,认真地说-不要这样做。




Answer 4 Tomasz Zieliński


我认为以上答案忽略了关键点。

让我们有一个带有方法的类。

class A(object):
    def m(self):
        pass

现在,让我们用ipython来玩玩它。

In [2]: A.m
Out[2]: <unbound method A.m>

好的,因此m()以某种方式成为A的未绑定方法。但是真的是那样吗?

In [5]: A.__dict__['m']
Out[5]: <function m at 0xa66b8b4>

事实证明,m()只是一个函数,对它的引用已添加到A类字典中-没有魔术。那为什么要给我们一个不受约束的方法呢?这是因为该点未转换为简单的字典查找。实际上是A .__ class __.__ getattribute __(A,'m')的调用:

In [11]: class MetaA(type):
   ....:     def __getattribute__(self, attr_name):
   ....:         print str(self), '-', attr_name

In [12]: class A(object):
   ....:     __metaclass__ = MetaA

In [23]: A.m
<class '__main__.A'> - m
<class '__main__.A'> - m

现在,我不清楚为什么最后一行要印两次,但还是很清楚是怎么回事。

现在,默认的__getattribute__所做的是检查属性是否为所谓的描述符,即,是否实现了特殊的__get__方法。如果实现该方法,则返回该__get__方法的结果。回到我们的A类的第一个版本,这是我们拥有的:

In [28]: A.__dict__['m'].__get__(None, A)
Out[28]: <unbound method A.m>

而且由于 Python 函数实现了描述符协议,如果代表一个对象被调用,它们就会在自己的 __get__方法中把自己绑定到这个对象上。

好吧,那么如何在一个现有的对象中添加一个方法呢?假设你不介意给类打补丁,就像这样简单。

B.m = m

然后,借助描述符魔术,Bm “成为”一个不受约束的方法。

而如果你想只在一个对象上添加一个方法,那么你必须自己模拟机械,通过使用 types.MethodType。

b.m = types.MethodType(m, b)

对了

In [2]: A.m
Out[2]: <unbound method A.m>

In [59]: type(A.m)
Out[59]: <type 'instancemethod'>

In [60]: type(b.m)
Out[60]: <type 'instancemethod'>

In [61]: types.MethodType
Out[61]: <type 'instancemethod'>



Answer 5 John Downey


from SomeOtherProduct.SomeModule import SomeClass
def speak(self):
   return "ook ook eee eee eee!"
SomeClass.speak = speak