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

oillo 发布于 2018-02-05 methods 最后更新 2018-02-05 01:11 1038 浏览

我读过,可以在Python中将方法添加到现有对象(例如不在类定义中)。 我明白这样做并不总是好事。但是,怎么会这样呢?

已邀请:

aet

赞同来自:

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

from SomeOtherProduct.SomeModule import SomeClass
def speak(self):
   return "ook ook eee eee eee!"
SomeClass.speak = speak
该代码将覆盖/创建一个叫做讲课的方法。 Jeff Atwood的recent post on monkey patching。他在C#3.0中展示了一个例子,它是我用于工作的当前语言。

ddolor

赞同来自:

我不知道Python的语法,但我知道Ruby可以做到这一点,这是微不足道的。比方说,你想添加一个方法到数组打印到标准输出的长度:

class Array
  def print_length
    puts length
  end
end
如果您不想修改整个类,则可以将该方法添加到数组的单个实例中,而其他数组将不具有该方法:
array = [1, 2, 3]
def array.print_length
  puts length
end
请注意使用此功能所涉及的问题。杰夫·阿特伍德(Jeff Atwood)实际上是wrote about it,不久之前。

eet

赞同来自:

在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'
有关更多信息,请参阅descriptorsmetaclass programming

hhic

赞同来自:

你要找的是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>

ab_non

赞同来自:

Jason Pratt发布的是正确的。

>>> 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()与()不同。在Python中,所有的方法都只是变量而已。

iaut

赞同来自:

模块 new 自python 2.6弃用,并在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'>

pculpa

赞同来自:

我认为上述答案错过了关键的一点。 让我们有一个方法的类:

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.m 给了我们一个不受约束的方法?这是因为这个点没有被翻译成简单的字典查找。事实上,A .__类的调用__.__ 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__所做的是检查属性是否是所谓的descriptor,即是否实现了一个特殊的__get__方法。如果它实现了这个方法,那么返回的是调用__get__方法的结果。回到我们的 A 类的第一个版本,这就是我们所拥有的:
In [28]: A.__dict__['m'].__get__(None, A)
Out[28]: <unbound method A.m>
而且由于Python函数实现了描述符协议,如果它们是代表对象调用的,则它们将自己绑定到__get__方法中的那个对象。 好的,那么如何将一个方法添加到现有的对象呢?假设你不介意修补课程,就像下面这样简单:
B.m = m
然后 B.m “变成”一个未绑定的方法,这要感谢描述符魔术。 如果你想添加一个方法只是一个单一的对象,那么你必须自己模拟机器,通过使用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'>

wnihil

赞同来自:

整合Jason Pratt和社区wiki的答案,看看不同的绑定方法的结果: 特别注意如何将绑定函数添加为类方法起作用,但是引用范围不正确。

#!/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>>
就个人而言,我更喜欢外部的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>>

icum

赞同来自:

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

a.methodname = function () { console.log("Yay, a new method!") }

wab

赞同来自:

在没有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>
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>>
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>>
有用的链接:
Data model - invoking descriptors结果 Descriptor HowTo Guide - invoking descriptors

ksit

赞同来自:

你们真的应该看看forbidden fruit,它是一个python库,它提供了对猴子修补任何python类,甚至字符串的支持。

ysunt

赞同来自:

如果可以帮忙的话,我最近发布了一个名为Gorilla的Python库,使猴子补丁的过程更加方便。 使用函数needle()修补名为guineapig的模块如下所示:

import gorilla
import guineapig
@gorilla.patch(guineapig)
def needle():
    print("awesome")
但是它也会处理documentationFAQ所示的更有趣的用例。 代码在GitHub上可用。

tin

赞同来自:

您可以使用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()
这是实例字符串 进程使用退出码0结束

grem

赞同来自:

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?
是的,这是可能的。 (但不建议。) 既然这是有启发性的,我会告诉你一些这样做的方法。 这里有一些设置代码。我们需要一个类的定义。它可以被导入,但它并不重要。
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
用法:
>>> 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

声明 请注意,只是因为这是可能的不建议。事实上,我建议你不要这样做,除非你有一个很好的理由。在类定义中定义正确的方法要好得多,或者更不用说直接对类进行修改,如下所示:
Foo.sample_method = sample_method

nin

赞同来自:

这实际上是“Jason Pratt”的答案 虽然Jasons回答了作品,但只有在想要为课堂添加功能时才有效。 当我尝试从.py源代码文件重新加载已经存在的方法时,它并不适用于我。 我花了很长时间才找到解决办法,但这个技巧似乎很简单... 1.st从源代码文件导入代码 2.强制重装 3.使用types.FunctionType(...)将导入和绑定的方法转换为函数 您也可以传递当前的全局变量,因为重新加载的方法将位于不同的名称空间中 4.现在你可以继续按照“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

nautem

赞同来自:

这个问题是在几年前打开的,但是,有一个简单的方法来模拟一个函数与使用装饰器的类实例的绑定:

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 ()
在那里,当你将函数和实例传递给binder装饰器时,它将创建一个新的函数,它具有与第一个相同的代码对象。然后,该类的给定实例被存储在新创建的函数的属性中。装饰器返回一个(第三个)自动调用复制函数的函数,给出实例作为第一个参数。   
  
总之,你会得到一个模拟绑定到类实例的函数。让原来的功能不变。

xsint

赞同来自:

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

def addmethod(obj, name, func):
    klass = obj.__class__
    subclass = type(klass.__name__, (klass,), {})
    setattr(subclass, name, func)
    obj.__class__ = subclass

iqui

赞同来自:

from types import MethodType
def method(self):
   print 'hi!'
setattr( targetObj, method.__name__, MethodType(method, targetObj, type(method)) )
有了这个,你可以使用自指针