题
我读到可以在 Python 中向现有对象(即不在类定义中)添加方法。
我知道这样做并不总是好的。但如何才能做到这一点呢?
解决方案
在 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>
为了绑定它,我们可以使用 types 模块中的 MethodType 函数:
>>> 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'
其他提示
模块 新的 自 python 2.6 起已弃用并在 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'>
前言 - 关于兼容性的说明:其他答案可能只适用于 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
现在我们将该方法添加到实例中。为此,我们需要 MethodType 构造函数 types
模块(我们上面导入的)。
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
结论
您现在知道了几种方法 可以 这样做,但严肃地说 - 不要这样做。
我觉得上面的回答都没有抓住重点。
让我们有一个带有方法的类:
class A(object):
def m(self):
pass
现在,让我们在 ipython 中使用它:
In [2]: A.m
Out[2]: <unbound method A.m>
好吧,那么 米() 以某种方式成为一个未绑定的方法 A. 。但真的是这样吗?
In [5]: A.__dict__['m']
Out[5]: <function m at 0xa66b8b4>
事实证明 米() 只是一个函数,对其的引用被添加到 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
然后 体重 由于描述符的魔力,“成为”一个未绑定的方法。
如果您只想向单个对象添加方法,那么您必须使用 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'>
在 Python 中,猴子修补通常是通过用您自己的签名覆盖类或函数签名来实现的。下面是一个来自 佐普维基:
from SomeOtherProduct.SomeModule import SomeClass
def speak(self):
return "ook ook eee eee eee!"
SomeClass.speak = speak
该代码将覆盖/创建类上名为 talk 的方法。在杰夫·阿特伍德的 最近关于猴子补丁的帖子. 。他展示了 C# 3.0 的示例,这是我当前工作中使用的语言。
至少有两种方法可以将方法附加到实例而不需要 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>>
有用的链接:
数据模型 - 调用描述符
描述符操作指南 - 调用描述符
您可以使用 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()
输出:
This is instance string
您正在寻找的是 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>
由于这个问题要求非 Python 版本,因此这里是 JavaScript:
a.methodname = function () { console.log("Yay, a new method!") }
整合 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>>
你们真的应该看看 禁果, ,它是一个 python 库,支持猴子修补任何 python 类,甚至字符串。
这实际上是“Jason Pratt”答案的附加内容
尽管 Jason 的答案有效,但只有在想向类添加函数时它才有效。当我尝试从 .py 源代码文件重新加载已经存在的方法时,它对我不起作用。
我花了很长时间才找到解决方法,但技巧似乎很简单......1.ST从源代码文件中导入代码2.nd强制重新加载3.RD使用types.functionType(...)将导入和绑定的方法转换为您也可以传递当前全局变量的函数,如重新加载的方法将在不同的命名空间4中。现在您可以使用types.methodtype(...)按照“ jason pratt”的建议继续。
例子:
# 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
杰森普拉特发布的内容是正确的。
>>> 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 中,所有方法都只是恰好是函数的变量。
这个问题是几年前提出的,但是嘿,有一种简单的方法可以使用装饰器模拟函数与类实例的绑定:
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 ()
在那里,当您将函数和实例传递给绑定器装饰器时,它将创建一个新函数,其代码对象与第一个函数相同。然后,该类的给定实例存储在新创建的函数的属性中。装饰器返回一个(第三个)函数,自动调用复制的函数,并将实例作为第一个参数。
总之,您得到一个模拟它绑定到类实例的函数。保持原来的功能不变。
我觉得奇怪的是,没有人提到上面列出的所有方法都会在添加的方法和实例之间创建循环引用,导致对象在垃圾回收之前一直保持不变。有一个老技巧通过扩展对象的类来添加描述符:
def addmethod(obj, name, func):
klass = obj.__class__
subclass = type(klass.__name__, (klass,), {})
setattr(subclass, name, func)
obj.__class__ = subclass
from types import MethodType
def method(self):
print 'hi!'
setattr( targetObj, method.__name__, MethodType(method, targetObj, type(method)) )
这样,您就可以使用 self 指针