Adding a Method to an Existing Object Instance
我已经读到可以在Python中向现有对象(即,不在类定义中)添加方法。
我知道这样做并不总是好的。但怎么能做到呢?
在Python中,函数和绑定方法之间存在差异。
1 2 3 4 5 6 7 8 9 10 11 12 13 | >>> 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>> >>> |
绑定方法已"绑定"(如何描述)到实例,并且每当调用该方法时,该实例将作为第一个参数传递。
但是,作为类属性(与实例相反)的可调用文件仍然是未绑定的,因此您可以随时修改类定义:
1 2 3 4 5 6 7 8 9 | >>> 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 |
以前定义的实例也会更新(只要它们没有重写属性本身):
1 2 | >>> a.fooFighters() fooFighters |
当您要将方法附加到单个实例时,会出现问题:
1 2 3 4 5 6 7 8 | >>> 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) |
当函数直接附加到实例时,它不会自动绑定:
1 2 | >>> a.barFighters <function barFighters at 0x00A98EF0> |
要绑定它,我们可以使用类型模块中的methodType函数:
1 2 3 4 5 6 | >>> import types >>> a.barFighters = types.MethodType( barFighters, a ) >>> a.barFighters <bound method ?.barFighters of <__main__.A instance at 0x00A9BC88>> >>> a.barFighters() barFighters |
这次类的其他实例没有受到影响:
1 2 3 4 | >>> a2.barFighters() Traceback (most recent call last): File"<stdin>", line 1, in <module> AttributeError: A instance has no attribute 'barFighters' |
通过阅读描述符和元类编程,可以找到更多信息。
自python 2.6以来,不推荐使用module new,而在3.0中删除了module new,请使用类型
请参阅http://docs.python.org/library/new.html
在下面的示例中,我故意从
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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'> |
序言-关于兼容性的注释:其他答案可能只在python 2中有效-这个答案在python 2和3中应该很好地工作。如果只编写python 3,可能会忽略显式继承
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?
是的,可以-但不推荐
我不推荐这个。这是个坏主意。不要这样做。
原因如下:
- 您将向执行此操作的每个实例添加绑定对象。如果你经常这样做,你可能会浪费很多记忆。绑定方法通常只在其调用的短时间内创建,然后在自动垃圾收集时停止存在。如果手动执行此操作,您将拥有一个引用绑定方法的名称绑定-这将防止使用时对其进行垃圾收集。
- 给定类型的对象实例通常在该类型的所有对象上都有其方法。如果在其他地方添加方法,某些实例将具有这些方法,而其他实例则不具有。程序员不会想到这一点,并且您有违反最不令人吃惊规则的风险。
- 因为还有其他很好的理由不这样做,如果你这样做的话,你会给自己一个很差的名声。
因此,我建议你不要这样做,除非你有一个很好的理由。最好在类定义中定义正确的方法,或者更不可取的是直接对类进行monkey修补,如下所示:
1 | Foo.sample_method = sample_method |
不过,既然这是有启发性的,我将向你们展示一些方法。
如何做到这是一些设置代码。我们需要一个类定义。它可以进口,但实际上并不重要。
1 2 | class Foo(object): '''An empty class to demonstrate adding a method to an instance''' |
创建实例:
1 | foo = Foo() |
创建要添加到其中的方法:
1 2 | def sample_method(self, bar, baz): print(bar + baz) |
方法0——使用描述符方法
对函数的点式查找将函数的
1 | foo.sample_method = sample_method.__get__(foo) |
现在:
1 2 | >>> foo.sample_method(1,2) 3 |
方法一-类型。方法类型
首先,导入类型,从中我们将获得方法构造函数:
1 | import types |
现在我们将该方法添加到实例中。为此,我们需要来自
types.methodType的参数签名是
1 | foo.sample_method = types.MethodType(sample_method, foo, Foo) |
用途:
1 2 | >>> foo.sample_method(1,2) 3 |
方法二:词汇结合
首先,我们创建一个将方法绑定到实例的包装函数:
1 2 3 4 | def bind(instance, method): def binding_scope_fn(*args, **kwargs): return method(instance, *args, **kwargs) return binding_scope_fn |
用途:
1 2 3 | >>> foo.sample_method = bind(foo, sample_method) >>> foo.sample_method(1,2) 3 |
方法三:functools.partial
分部函数将第一个参数应用于函数(和可选的关键字参数),然后可以使用其余参数(和重写关键字参数)调用。因此:
1 2 3 4 | >>> from functools import partial >>> foo.sample_method = partial(sample_method, foo) >>> foo.sample_method(1,2) 3 |
当您认为绑定方法是实例的部分函数时,这是有意义的。
作为对象属性的未绑定函数-为什么不起作用:如果我们尝试以与向类中添加示例方法相同的方式添加该示例方法,那么它将与实例解除绑定,并且不会将隐式self作为第一个参数。
1 2 3 4 5 | >>> 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) |
我们可以通过显式地传递实例(或任何东西)使未绑定函数工作,因为此方法实际上不使用
1 2 | >>> foo.sample_method(foo, 1, 2) 3 |
结论
你现在知道有几种方法可以做到这一点,但严肃地说,不要这样做。
我认为上述答案没有抓住关键点。
让我们用一个方法来创建一个类:
1 2 3 | class A(object): def m(self): pass |
现在,让我们在ipython中使用它:
1 2 | In [2]: A.m Out[2]: <unbound method A.m> |
好吧,那么m()不知何故变成了a的未绑定方法,但它真的是这样吗?
1 2 | In [5]: A.__dict__['m'] Out[5]: <function m at 0xa66b8b4> |
结果发现m()只是一个函数,对它的引用被添加到类字典中——这没有什么魔力。那为什么A.M给了我们一个未绑定的方法呢?这是因为点没有翻译成简单的字典查找。事实上,它是一个"类"的调用。
1 2 3 4 5 6 7 8 9 10 | 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 |
现在,我不太清楚为什么最后一行要打印两次,但仍然很清楚到底发生了什么。
现在,默认的get attribute所做的是检查该属性是否是所谓的描述符,即它是否实现了一个特殊的get attribute方法。如果它实现了这个方法,那么返回的结果就是调用这个get方法的结果。回到我们的A类的第一个版本,我们有:
1 2 | In [28]: A.__dict__['m'].__get__(None, A) Out[28]: <unbound method A.m> |
因为python函数实现了描述符协议,所以如果它们是代表一个对象被调用的,那么它们将自己绑定到它们的get方法中的那个对象上。
好的,那么如何向现有对象添加方法呢?假设你不介意修补类,那么简单的方法如下:
1 | B.m = m |
然后,由于描述符的魔力,b.m"成为"一个未绑定的方法。
如果您只想将一个方法添加到一个对象中,那么您必须使用types.methodtype自己模拟机器:
1 | b.m = types.MethodType(m, b) |
顺便说一句:
1 2 3 4 5 6 7 8 9 10 11 | 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'> |
在Python中,monkey修补通常通过用自己的类或函数签名覆盖来工作。以下是zope wiki的一个示例:
1 2 3 4 | from SomeOtherProduct.SomeModule import SomeClass def speak(self): return"ook ook eee eee eee!" SomeClass.speak = speak |
该代码将覆盖/创建一个名为speak的类方法。在杰夫·阿特伍德最近发表的关于修补猴子的文章中。他在C 3.0中展示了一个例子,这是我工作中使用的当前语言。
至少有两种方法可以将方法附加到不带
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | >>> 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:
1 2 3 4 5 | >>> 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:
1 2 3 4 5 6 7 8 | >>> 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>> |
有用链接:数据模型-调用描述符描述符指南-调用描述符
可以使用lambda将方法绑定到实例:
1 2 3 4 5 6 7 8 9 10 | 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
你要找的是
1 2 3 4 5 6 | >>> 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> |
由于这个问题要求非python版本,下面是javascript:
1 | a.methodname = function () { console.log("Yay, a new method!") } |
整合Jason Pratt和社区wiki答案,看看不同绑定方法的结果:
特别要注意如何将绑定函数添加为类方法,但引用范围不正确。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 | #!/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 ' attributes 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 ' attributes 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 ' attributes 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函数路由,因为它允许我在迭代器中动态地分配新的方法名。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | def y(self, x): pass d = C() for i in range(1,5): ADDMETHOD(d, y, 'f%d' % i) print ' attributes 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>> |
这实际上是对"杰森·普拉特"答案的补充。
尽管JasonsAnswer有效,但它只在希望向类中添加函数时有效。当我试图从.py源代码文件重新加载一个已经存在的方法时,它不起作用。
我花了很长时间才找到一个解决方法,但这个方法似乎很简单…1.st从源代码文件导入代码2.nd强制重新加载3.rd使用types.functiontype(…)将导入和绑定的方法转换为函数还可以传递当前全局变量,因为重新加载的方法将位于不同的命名空间中。4.现在你可以按照"杰森·普拉特"的建议继续。使用types.methodType(…)
例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | # 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 |
你们真的应该看看禁果,它是一个python库,支持monkey修补任何python类,甚至字符串。
如果有什么帮助的话,我最近发布了一个名为Gorilla的Python库,以使猴子修补的过程更加方便。
使用函数
1 2 3 4 5 | import gorilla import guineapig @gorilla.patch(guineapig) def needle(): print("awesome") |
但是它也处理更有趣的用例,如文档中常见问题解答中所示。
代码在GitHub上可用。
杰森·普拉特的文章是正确的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | >>> 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()与a()有任何不同。在Python中,所有方法都只是碰巧是函数的变量。
1 2 3 4 5 6 7 | from types import MethodType def method(self): print 'hi!' setattr( targetObj, method.__name__, MethodType(method, targetObj, type(method)) ) |
有了这个,您可以使用自指针
这个问题几年前就开始了,但是,有一个简单的方法可以使用decorator模拟函数到类实例的绑定:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 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 () |
在这里,当您将函数和实例传递给绑定器装饰器时,它将创建一个新的函数,其代码对象与第一个相同。然后,类的给定实例存储在新创建的函数的属性中。decorator返回一个(第三个)函数,自动调用复制的函数,并将实例作为第一个参数。< BR>< BR>总之,您得到一个函数来模拟它与类实例的绑定。保持原来的功能不变。
我觉得奇怪的是,没有人提到上面列出的所有方法都在添加的方法和实例之间创建了一个循环引用,导致对象一直持续到垃圾收集。通过扩展对象的类来添加描述符有一个古老的技巧:
1 2 3 4 5 | def addmethod(obj, name, func): klass = obj.__class__ subclass = type(klass.__name__, (klass,), {}) setattr(subclass, name, func) obj.__class__ = subclass |