将方法添加到现有对象实例

akd*_*dom 596 python oop methods monkeypatching

我已经读过可以在Python中向现有对象(即,不在类定义中)添加方法.

我知道这样做并不总是好事.但是人们怎么可能这样做呢?

Jas*_*att 873

在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>>
>>>
Run Code Online (Sandbox Code Playgroud)

绑定方法已被"绑定"(如何描述)实例,并且只要调用该方法,该实例将作为第一个参数传递.

作为类的属性(而不是实例)的可调用对象仍然是未绑定的,因此您可以随时修改类定义:

>>> 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
Run Code Online (Sandbox Code Playgroud)

以前定义的实例也会更新(只要它们没有覆盖属性本身):

>>> a.fooFighters()
fooFighters
Run Code Online (Sandbox Code Playgroud)

当您想要将方法附加到单个实例时,会出现问题:

>>> 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)
Run Code Online (Sandbox Code Playgroud)

当函数直接附加到实例时,该函数不会自动绑定:

>>> a.barFighters
<function barFighters at 0x00A98EF0>
Run Code Online (Sandbox Code Playgroud)

要绑定它,我们可以在types模块中使用MethodType函数:

>>> import types
>>> a.barFighters = types.MethodType( barFighters, a )
>>> a.barFighters
<bound method ?.barFighters of <__main__.A instance at 0x00A9BC88>>
>>> a.barFighters()
barFighters
Run Code Online (Sandbox Code Playgroud)

这次该类的其他实例未受到影响:

>>> a2.barFighters()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: A instance has no attribute 'barFighters'
Run Code Online (Sandbox Code Playgroud)

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

  • 而不是手动创建`MethodType`,手动调用[描述符协议](https://docs.python.org/2/howto/descriptor.html)并让函数生成您的实例:`barFighters .__ get __(a) `为`a`绑定`barFighters`生成绑定方法. (57认同)
  • 建议的描述符方法的完整代码是`a.barFighters = barFighters .__ get __(a)` (25认同)
  • @EndermanAPM:有几个:它更可能继续与访问实例上的属性完全相同.它也适用于`classmethod`和`staticmethod`以及其他描述符.它避免了使用另一个导入来混淆命名空间. (13认同)
  • @MartijnPieters 除了可能更具可读性之外,使用“描述符协议”与创建“MethodType”的任何优势。 (3认同)
  • 请注意,python3 中没有未绑定方法,因为函数和未绑定方法之间的差异非常小,Python 3 消除了这种区别。 (3认同)

Evg*_*eny 93

从python 2.6开始不推荐使用Module new,在3.0中删除它,使用类型

http://docs.python.org/library/new.html

在下面的例子中,我故意从patch_me()函数中删除了返回值.我认为给出返回值可能会让人相信补丁返回一个新对象,这不是真的 - 它会修改传入的对象.这可能有助于更有纪律地使用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'>
Run Code Online (Sandbox Code Playgroud)

  • 如果添加的方法需要引用 self,这将如何工作?我的第一次尝试导致语法错误,但在方法的定义中添加 self 似乎不起作用。导入类型 class A(object):#but 似乎也适用于旧样式对象 ax = 'ax' pass def patch_me(target): def method(target,x): print (self.ax) print ("x=" ,x) print ("called from", target) target.method = types.MethodType(method,target) #如果需要的话添加更多 a = A() print(a.ax) (3认同)

Aar*_*all 78

前言 - 关于兼容性的说明:其他答案可能只适用于Python 2 - 这个答案应该在Python 2和3中运行得非常好.如果只编写Python 3,你可能会遗漏明确继承object,但是代码应该保持不变.

将方法添加到现有对象实例

我已经读过可以在Python中向现有对象(例如,不在类定义中)添加方法.

我知道这样做并不总是一个好的决定.但是,一个人怎么可能这样做?

是的,有可能 - 但不推荐

我不推荐这个.这是一个坏主意.不要这样做.

这有几个原因:

  • 您将为每个执行此操作的实例添加绑定对象.如果你这么做,你可能会浪费很多记忆.绑定方法通常仅在其调用的短持续时间内创建,然后在自动垃圾收集时停止存在.如果您手动执行此操作,您将具有引用绑定方法的名称绑定 - 这将阻止其在使用时进行垃圾回收.
  • 给定类型的对象实例通常在该类型的所有对象上都有其方法.如果在其他地方添加方法,则某些实例将具有这些方法,而其他实例则不会.程序员不会期望这样,你冒着违反最小惊喜规则的风险.
  • 由于还有其他很好的理由不这样做,如果你这样做,你还会给自己一个不好的声誉.

因此,我建议你不要这样做,除非你有充分的理由.最好在类定义中定义正确的方法,或者更不用说直接对类进行修补,如下所示:

Foo.sample_method = sample_method
Run Code Online (Sandbox Code Playgroud)

但是,由于它具有指导意义,我将向您展示一些方法.

如何做到这一点

这是一些设置代码.我们需要一个类定义.它可以导入,但它确实无关紧要.

class Foo(object):
    '''An empty class to demonstrate adding a method to an instance'''
Run Code Online (Sandbox Code Playgroud)

创建一个实例:

foo = Foo()
Run Code Online (Sandbox Code Playgroud)

创建一个添加到其中的方法:

def sample_method(self, bar, baz):
    print(bar + baz)
Run Code Online (Sandbox Code Playgroud)

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

函数上的虚线查找__get__使用实例调用函数的方法,将对象绑定到方法,从而创建"绑定方法".

foo.sample_method = sample_method.__get__(foo)
Run Code Online (Sandbox Code Playgroud)

现在:

>>> foo.sample_method(1,2)
3
Run Code Online (Sandbox Code Playgroud)

方法一 - types.MethodType

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

import types
Run Code Online (Sandbox Code Playgroud)

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

types.MethodType的参数签名是(function, instance, class):

foo.sample_method = types.MethodType(sample_method, foo, Foo)
Run Code Online (Sandbox Code Playgroud)

和用法:

>>> foo.sample_method(1,2)
3
Run Code Online (Sandbox Code Playgroud)

方法二:词法绑定

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

def bind(instance, method):
    def binding_scope_fn(*args, **kwargs): 
        return method(instance, *args, **kwargs)
    return binding_scope_fn
Run Code Online (Sandbox Code Playgroud)

用法:

>>> foo.sample_method = bind(foo, sample_method)    
>>> foo.sample_method(1,2)
3
Run Code Online (Sandbox Code Playgroud)

方法三:functools.partial

部分函数将第一个参数应用于函数(以及可选的关键字参数),稍后可以使用其余参数调用(并覆盖关键字参数).从而:

>>> from functools import partial
>>> foo.sample_method = partial(sample_method, foo)
>>> foo.sample_method(1,2)
3    
Run Code Online (Sandbox Code Playgroud)

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

取消绑定功能作为对象属性 - 为什么这不起作用:

如果我们尝试以与我们可能将它添加到类相同的方式添加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)
Run Code Online (Sandbox Code Playgroud)

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

>>> foo.sample_method(foo, 1, 2)
3
Run Code Online (Sandbox Code Playgroud)

结论

你现在知道几种方法可以做到这一点,但严肃地说 - 不要这样做.

  • @AidasBendoraitis我不会说它"需要"它,它是应用描述符协议时提供的可选参数 - 但是python函数不使用参数:https://github.com/python/cpython/blob/master /Objects/funcobject.c#L581 (2认同)
  • @AaronHall:我认为这实际上在模拟时是一种有用的技术。比如说,您想使用已定义的实现来对模拟对象进行猴子修补。人们很容易认为 `mock.sample_method = SampleClass.sample_method` 就可以工作...... (2认同)

Tom*_*ski 34

我认为上述答案错过了关键点.

让我们有一个方法的类:

class A(object):
    def m(self):
        pass
Run Code Online (Sandbox Code Playgroud)

现在,让我们在ipython中使用它:

In [2]: A.m
Out[2]: <unbound method A.m>
Run Code Online (Sandbox Code Playgroud)

好吧,所以m()不知何故成为A的未绑定方法.但它真的像那样吗?

In [5]: A.__dict__['m']
Out[5]: <function m at 0xa66b8b4>
Run Code Online (Sandbox Code Playgroud)

事实证明,m()只是一个函数,它的引用被添加到A类字典中 - 没有魔法.那么为什么Am给我们一个未绑定的方法呢?这是因为点不会转换为简单的字典查找.事实上,调用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
Run Code Online (Sandbox Code Playgroud)

现在,我不确定为什么最后一行打印两次,但仍然清楚那里发生了什么.

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

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

并且因为Python函数实现了描述符协议,如果它们代表一个对象被调用,它们会在__get__方法中将它们绑定到该对象.

好的,那么如何将方法添加到现有对象?假设您不介意修补类,它就像下面这样简单:

B.m = m
Run Code Online (Sandbox Code Playgroud)

然后Bm "变成"一个未绑定的方法,这要归功于描述符魔术.

如果你想只为一个对象添加一个方法,那么你必须通过使用types.MethodType来自己模拟机器.

b.m = types.MethodType(m, b)
Run Code Online (Sandbox Code Playgroud)

顺便说说:

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'>
Run Code Online (Sandbox Code Playgroud)


Joh*_*ney 17

在Python中,猴子修补通常通过用自己的方法覆盖类或函数签名来工作.以下是Zope Wiki的一个例子:

from SomeOtherProduct.SomeModule import SomeClass
def speak(self):
   return "ook ook eee eee eee!"
SomeClass.speak = speak
Run Code Online (Sandbox Code Playgroud)

该代码将覆盖/创建一个名为speak的方法.在Jeff Atwood 最近关于猴子补丁帖子中.他在C#3.0中展示了一个例子,它是我用于工作的当前语言.

  • 但它会影响班级的所有*实例,而不仅仅是一个. (6认同)

Evg*_*rat 11

您可以使用lambda将方法绑定到实例:

def run(self):
    print self._instanceString

class A(object):
    def __init__(self):
        self._instanceString = "This is instance string"

a = A()
a.run = lambda: run(a)
a.run()
Run Code Online (Sandbox Code Playgroud)

这是实例字符串

进程以退出代码0结束


ndp*_*dpu 9

在没有types.MethodType以下情况的情况下,至少有两种方法可以将方法附加到实例:

>>> class A:
...  def m(self):
...   print 'im m, invoked with: ', self

>>> a = A()
>>> a.m()
im m, invoked with:  <__main__.A instance at 0x973ec6c>
>>> a.m
<bound method A.m of <__main__.A instance at 0x973ec6c>>
>>> 
>>> def foo(firstargument):
...  print 'im foo, invoked with: ', firstargument

>>> foo
<function foo at 0x978548c>
Run Code Online (Sandbox Code Playgroud)

1:

>>> a.foo = foo.__get__(a, A) # or foo.__get__(a, type(a))
>>> a.foo()
im foo, invoked with:  <__main__.A instance at 0x973ec6c>
>>> a.foo
<bound method A.foo of <__main__.A instance at 0x973ec6c>>
Run Code Online (Sandbox Code Playgroud)

2:

>>> instancemethod = type(A.m)
>>> instancemethod
<type 'instancemethod'>
>>> a.foo2 = instancemethod(foo, a, type(a))
>>> a.foo2()
im foo, invoked with:  <__main__.A instance at 0x973ec6c>
>>> a.foo2
<bound method instance.foo of <__main__.A instance at 0x973ec6c>>
Run Code Online (Sandbox Code Playgroud)

有用的链接:
数据模型 - 调用描述符
Descriptor HowTo Guide - 调用描述符


HS.*_*HS. 6

setattr我相信你所寻找的是什么.使用此选项可在对象上设置属性.

>>> def printme(s): print repr(s)
>>> class A: pass
>>> setattr(A,'printme',printme)
>>> a = A()
>>> a.printme() # s becomes the implicit 'self' variable
< __ main __ . A instance at 0xABCDEFG>
Run Code Online (Sandbox Code Playgroud)

  • 这是修补类`A`,而不是实例`a`. (7认同)
  • 是否有理由使用`setattr(A,'printme',printme)`而不是简单的`A.printme = printme`? (5认同)
  • 如果在运行时构造方法名称是有道理的。 (2认同)

Tam*_*ake 6

由于这个问题要求非Python版本,这里是JavaScript:

a.methodname = function () { console.log("Yay, a new method!") }
Run Code Online (Sandbox Code Playgroud)


Nis*_*n.H 5

整合Jason Pratt和社区维基的答案,看看不同绑定方法的结果:

特别要注意如何添加绑定功能作为一个类的方法的工作原理,但引用范围不正确.

#!/usr/bin/python -u
import types
import inspect

## dynamically adding methods to a unique instance of a class


# get a list of a class's method type attributes
def listattr(c):
    for m in [(n, v) for n, v in inspect.getmembers(c, inspect.ismethod) if isinstance(v,types.MethodType)]:
        print m[0], m[1]

# externally bind a function as a method of an instance of a class
def ADDMETHOD(c, method, name):
    c.__dict__[name] = types.MethodType(method, c)

class C():
    r = 10 # class attribute variable to test bound scope

    def __init__(self):
        pass

    #internally bind a function as a method of self's class -- note that this one has issues!
    def addmethod(self, method, name):
        self.__dict__[name] = types.MethodType( method, self.__class__ )

    # predfined function to compare with
    def f0(self, x):
        print 'f0\tx = %d\tr = %d' % ( x, self.r)

a = C() # created before modified instnace
b = C() # modified instnace


def f1(self, x): # bind internally
    print 'f1\tx = %d\tr = %d' % ( x, self.r )
def f2( self, x): # add to class instance's .__dict__ as method type
    print 'f2\tx = %d\tr = %d' % ( x, self.r )
def f3( self, x): # assign to class as method type
    print 'f3\tx = %d\tr = %d' % ( x, self.r )
def f4( self, x): # add to class instance's .__dict__ using a general function
    print 'f4\tx = %d\tr = %d' % ( x, self.r )


b.addmethod(f1, 'f1')
b.__dict__['f2'] = types.MethodType( f2, b)
b.f3 = types.MethodType( f3, b)
ADDMETHOD(b, f4, 'f4')


b.f0(0) # OUT: f0   x = 0   r = 10
b.f1(1) # OUT: f1   x = 1   r = 10
b.f2(2) # OUT: f2   x = 2   r = 10
b.f3(3) # OUT: f3   x = 3   r = 10
b.f4(4) # OUT: f4   x = 4   r = 10


k = 2
print 'changing b.r from {0} to {1}'.format(b.r, k)
b.r = k
print 'new b.r = {0}'.format(b.r)

b.f0(0) # OUT: f0   x = 0   r = 2
b.f1(1) # OUT: f1   x = 1   r = 10  !!!!!!!!!
b.f2(2) # OUT: f2   x = 2   r = 2
b.f3(3) # OUT: f3   x = 3   r = 2
b.f4(4) # OUT: f4   x = 4   r = 2

c = C() # created after modifying instance

# let's have a look at each instance's method type attributes
print '\nattributes of a:'
listattr(a)
# OUT:
# attributes of a:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x000000000230FD88>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x000000000230FD88>>
# f0 <bound method C.f0 of <__main__.C instance at 0x000000000230FD88>>

print '\nattributes of b:'
listattr(b)
# OUT:
# attributes of b:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x000000000230FE08>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x000000000230FE08>>
# f0 <bound method C.f0 of <__main__.C instance at 0x000000000230FE08>>
# f1 <bound method ?.f1 of <class __main__.C at 0x000000000237AB28>>
# f2 <bound method ?.f2 of <__main__.C instance at 0x000000000230FE08>>
# f3 <bound method ?.f3 of <__main__.C instance at 0x000000000230FE08>>
# f4 <bound method ?.f4 of <__main__.C instance at 0x000000000230FE08>>

print '\nattributes of c:'
listattr(c)
# OUT:
# attributes of c:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x0000000002313108>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x0000000002313108>>
# f0 <bound method C.f0 of <__main__.C instance at 0x0000000002313108>>
Run Code Online (Sandbox Code Playgroud)

就个人而言,我更喜欢外部ADDMETHOD函数路由,因为它允许我在迭代器中动态分配新的方法名称.

def y(self, x):
    pass
d = C()
for i in range(1,5):
    ADDMETHOD(d, y, 'f%d' % i)
print '\nattributes of d:'
listattr(d)
# OUT:
# attributes of d:
# __init__ <bound method C.__init__ of <__main__.C instance at 0x0000000002303508>>
# addmethod <bound method C.addmethod of <__main__.C instance at 0x0000000002303508>>
# f0 <bound method C.f0 of <__main__.C instance at 0x0000000002303508>>
# f1 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>
# f2 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>
# f3 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>
# f4 <bound method ?.y of <__main__.C instance at 0x0000000002303508>>
Run Code Online (Sandbox Code Playgroud)


Max*_*Max 5

这实际上是"Jason Pratt"答案的补充.

尽管Jasons的答案有效,但只有在想要为类添加函数时它才有效.当我尝试从.py源代码文件重新加载已存在的方法时,它对我不起作用.

我花了很长时间才找到一个解决方法,但技巧似乎很简单...... 1.从源代码文件中导入代码2.nd强制重新加载3.rd使用types.FunctionType(...)来转换函数的导入和绑定方法也可以传递给当前的全局变量,因为重新加载的方法将位于不同的命名空间中.现在你可以按照"Jason Pratt"的建议继续使用types.MethodType(... )

例:

# this class resides inside ReloadCodeDemo.py
class A:
    def bar( self ):
        print "bar1"

    def reloadCode(self, methodName):
        ''' use this function to reload any function of class A'''
        import types
        import ReloadCodeDemo as ReloadMod # import the code as module
        reload (ReloadMod) # force a reload of the module
        myM = getattr(ReloadMod.A,methodName) #get reloaded Method
        myTempFunc = types.FunctionType(# convert the method to a simple function
                                myM.im_func.func_code, #the methods code
                                globals(), # globals to use
                                argdefs=myM.im_func.func_defaults # default values for variables if any
                                ) 
        myNewM = types.MethodType(myTempFunc,self,self.__class__) #convert the function to a method
        setattr(self,methodName,myNewM) # add the method to the function

if __name__ == '__main__':
    a = A()
    a.bar()
    # now change your code and save the file
    a.reloadCode('bar') # reloads the file
    a.bar() # now executes the reloaded code
Run Code Online (Sandbox Code Playgroud)


lai*_*ain 5

这个问题是几年前提出的,但是嘿,有一种简单的方法可以使用装饰器模拟函数与类实例的绑定:

def binder (function, instance):
  copy_of_function = type (function) (function.func_code, {})
  copy_of_function.__bind_to__ = instance
  def bound_function (*args, **kwargs):
    return copy_of_function (copy_of_function.__bind_to__, *args, **kwargs)
  return bound_function


class SupaClass (object):
  def __init__ (self):
    self.supaAttribute = 42


def new_method (self):
  print self.supaAttribute


supaInstance = SupaClass ()
supaInstance.supMethod = binder (new_method, supaInstance)

otherInstance = SupaClass ()
otherInstance.supaAttribute = 72
otherInstance.supMethod = binder (new_method, otherInstance)

otherInstance.supMethod ()
supaInstance.supMethod ()
Run Code Online (Sandbox Code Playgroud)

在那里,当您将函数和实例传递给绑定器装饰器时,它将创建一个新函数,其代码对象与第一个函数相同。然后,该类的给定实例存储在新创建的函数的属性中。装饰器返回一个(第三个)函数,自动调用复制的函数,并将实例作为第一个参数。

总之,您得到一个模拟它绑定到类实例的函数。保持原来的功能不变。


小智 5

我觉得奇怪的是,没有人提到上面列出的所有方法都会在添加的方法和实例之间创建循环引用,导致对象在垃圾回收之前一直保持不变。有一个老技巧通过扩展对象的类来添加描述符:

def addmethod(obj, name, func):
    klass = obj.__class__
    subclass = type(klass.__name__, (klass,), {})
    setattr(subclass, name, func)
    obj.__class__ = subclass
Run Code Online (Sandbox Code Playgroud)