动态添加实例属性的时候,这样:obj.a = 1
或 setattr(obj, 'a', 1)
就可以了,so easy !
但是,动态添加方法的时候,问题来了。
这里动态的给一个实例添加一个方法:
>>> class A(object):
... pass
...
>>> def hello(self):
... print "hello"
...
>>> a = A()
>>> a.hello = hello
WTF?:
>>> a.hello()
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: hello() takes exactly 1 argument (0 given)
对于实例来说只是function
!连method
都不是……
>>> a.hello
<function hello at 0x7ff1693ab758>
>>> a.hello(1)
hello
这是什么情况我也不得而知……
参考 stackoverflow 上的最高票回答Adding a Method to an Existing Object。
对于一个类型(非实例)来说,它的属性都是unbound
的:
>>> class A(object):
... def hello(self):
... print "hello"
...
>>> A.hello
<unbound method A.hello>
>>> A().hello
<bound method A.hello of <__main__.A object at 0x7f171e986810>>
所以可以通过类型来动态添加一个方法,对于实例来说,它是bound
的,任何这个类型的实例都可以访问:
>>> class A(object):
... pass
...
>>> def hello(self):
... print 'hello'
...
>>> A.hello = hello
>>> A().hello()
hello
>>> A.hello
<unbound method A.hello>
>>> A().hello
<bound method A.hello of <__main__.A object at 0x7f62edae7810>>
另一种方式是使用types.MethodType,这一种只对当前实例起作用:
>>> import types
>>> class A(object):
... pass
...
>>> def hello(self):
... print hello
...
>>> a = A()
>>> a.hello = types.MethodType(hello, a)
>>> a.hello
<bound method ?.hello of <__main__.A object at 0x7fd8a318a290>>
>>> A.hello
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: type object 'A' has no attribute 'hello'
types模块是 2.6 版之后new模块的替代(Python 3里已经不存在 new 模块了),所以new
里面的函数types
模块都有替代,如:
>>> b = types.InstanceType(A)
>>> b
<__main__.A instance at 0x7fd8a318c488>
>>> b.hello = types.UnboundMethodType(hello, A)
>>> b.hello
<bound method ?.hello of <class __main__.A at 0x7fd8a315cc80>>
>>> A.hello
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: class A has no attribute 'hello'
这里忍不住要看一下types
的源码,很简短,但是你懂得… 涉及到type
的,脑子里都是空白一片
这里还是不懂是什么机制,不过另一个答案Adding a Method to an Existing Object提到了descriptor protocol
。
描述器是属性, 实例方法, 静态方法, 类方法和 super 的背后的实现机制。
参考Descriptor HowTo Guide及译文Python描述器引导(翻译)。
另外动态的添加staticmethod
,classmethod
的方式:
>>> class A(object):
... pass
...
>>> def hello():
... print 'hello'
...
>>> def world(cls):
... print 'world'
...
>>> A.hello = staticmethod(hello) # setattr(A, 'hello', staticmethod(hello))
>>> A.world = classmethod(world) # setattr(A, 'hello', classmethod(world))
>>> a = A()
>>> a.hello()
hello
>>> a.world()
world
>>> a.hello # A.hello 也是 function hello
<function hello at 0x7f39b4bf3758>
>>> a.world # A.world 也是 bound method classobj.world
<bound method classobj.world of <class __main__.A at 0x7f39b4bf0258>>
话说我是怎么掉进这个坑的?其实只是为了搞一个奇葩的装饰器…
这也正是动态语言的强大之处:
class AddHigh(object):
""" 给长方形添加高变成长方体 """
def __init__(self, cls, z):
self._cls = cls
self._z = z
def __call__(self, *args, **kwargs):
# 重写面积计算方式
def _area(this, *args, **kwargs):
return (self._z * this._x + self._z * this._y + this._x * this._y) * 2
self._cls.area = _area
obj = self._cls(*args, **kwargs)
return obj
class Rectangle(object):
""" 长方形 """
def __init__(self, x, y):
self._x = x
self._y = y
def area(self):
return self._x * self._y
if __name__ == '__main__':
a = Rectangle(1, 2)
print a.area()
b = AddHigh(Rectangle, 3)(1, 2)
print b.area()
# 2
# 22
Append
2015.11.2 -> python tornado中如何给每个服务器请求动态加上装饰器?@Damnever 的回答