向现有对象实例添加方法

2024-04-25 23:27:49 发布

您现在位置:Python中文网/ 问答频道 /正文


Tags: python
3条回答

在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 function in the types module

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

更多信息可以通过阅读descriptorsmetaclassprogramming找到。

由于python 2.6和3.0中的删除,模块new不推荐使用,请使用类型

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

序言—关于兼容性的说明:其他答案可能只适用于Python2—这个答案在Python2和3中应该非常有效。如果只编写Python 3,则可以省略从object显式继承,否则代码应该保持不变。

Adding a Method to an Existing Object Instance

I've read that it is possible to add a method to an existing object (e.g. not in the class definition) in Python.

I understand that it's not always a good decision to do so. But, how might one do this?

是的,这是可能的-但不推荐

我不推荐这个。这是个坏主意。别这么做。

有几个原因:

  • 您将向执行此操作的每个实例添加绑定对象。如果你经常这样做,你可能会浪费大量的记忆。绑定方法通常只在其调用的短时间内创建,然后在自动垃圾回收时不再存在。如果手动执行此操作,则会有一个引用绑定方法的名称绑定,这将阻止在使用时对其进行垃圾收集。
  • 给定类型的对象实例通常对该类型的所有对象都有其方法。如果在其他地方添加方法,则某些实例将具有这些方法,而其他实例则不会。程序员不会预料到这一点,您可能会违反rule of least surprise
  • 既然还有其他很好的理由不这么做,那么如果你这么做的话,你会给自己带来坏名声。

因此,我建议你不要这样做,除非你有一个很好的理由。最好在类定义中定义正确的方法,或者较少最好直接对类进行monkey修补,如下所示:

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

方法1-类型。方法类型

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

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

方法三:functools.partial

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

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

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

将函数作为对象属性解除绑定-为什么这不起作用:

如果我们试图以与将sample_方法添加到类中相同的方式添加sample_方法,那么它将从实例中解除绑定,并且不会将隐式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

结论

你现在知道你可以用几种方法来做这件事,但严肃地说,不要这样做。

相关问题 更多 >