我已经了解到,可以在Python中向现有对象(即,不在类定义中)添加方法。
我明白这样做并不总是好的。但你怎么能做到这一点呢?
我已经了解到,可以在Python中向现有对象(即,不在类定义中)添加方法。
我明白这样做并不总是好的。但你怎么能做到这一点呢?
当前回答
这实际上是对“杰森·普拉特”答案的补充
虽然Jasons的答案是有效的,但它只在想要向类中添加函数时有效。当我试图从.py源代码文件重新加载一个已经存在的方法时,它对我来说并不起作用。
我花了很长时间才找到解决方法,但技巧似乎很简单。。。1.从源代码文件导入代码2.强制重新加载3.rd使用types.FunctionType(…)将导入和绑定的方法转换为函数您还可以传递当前的全局变量,因为重新加载的方法将位于不同的命名空间中4.现在你可以按照“杰森·普拉特”的建议继续使用类型.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
其他回答
前言-关于兼容性的说明:其他答案可能只在Python2-这个答案应该在Python2和3中运行得很好。如果只编写Python3,您可能会忽略显式继承自对象,否则代码应该保持不变。
向现有对象实例添加方法我已经了解到,可以在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.MethodType(在Python 3中)的参数签名是(function,instance):
foo.sample_method = types.MethodType(sample_method, foo)
和用法:
>>> foo.sample_method(1,2)
3
附带地,在Python 2中,签名是(函数、实例、类):
foo.sample_method = types.MethodType(sample_method, foo, Foo)
方法二:词汇绑定
首先,我们创建一个将方法绑定到实例的包装函数:
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添加到类中相同的方式添加sample_methods,它将与实例绑定,并且不会将隐式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)
我们可以通过显式传递实例(或任何东西,因为此方法实际上不使用自参数变量)来使未绑定函数工作,但它与其他实例的预期签名不一致(如果我们正在对该实例进行猴子修补):
>>> foo.sample_method(foo, 1, 2)
3
结论
你现在知道有几种方法可以做到这一点,但认真地说,不要这样做。
如何从类的实例恢复类
class UnderWater:
def __init__(self):
self.net = 'underwater'
marine = UnderWater() # Instantiate the class
# Recover the class from the instance and add attributes to it.
class SubMarine(marine.__class__):
def __init__(self):
super().__init__()
self.sound = 'Sonar'
print(SubMarine, SubMarine.__name__, SubMarine().net, SubMarine().sound)
# Output
# (__main__.SubMarine,'SubMarine', 'underwater', 'Sonar')
在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函数:
>>> 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'
通过阅读描述符和元类编程可以找到更多信息。
可以使用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
整合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>>