Adding an instance attribute dynamically is as simple as: obj.a = 1
or setattr(obj, 'a', 1)
. So easy!
However, when it comes to dynamically adding methods, the problem arises.
Here, a method is dynamically added to an instance:
>>> 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)
For the instance, it’s just a function
! It’s not even a method
…
>>> a.hello
<function hello at 0x7ff1693ab758>
>>> a.hello(1)
hello
I’m not sure what’s going on here…
Referencing the top answer on Stackoverflow’s Adding a Method to an Existing Object.
For a type (not an instance), its attributes are 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>>
So, you can dynamically add a method through the type. For the instance, it is bound
, and any instance of this type can access it:
>>> 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>>
Another way is to use types.MethodType, which only works for the current instance:
>>> 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'
The types module is a replacement for the new module after version 2.6 (the new module does not exist in Python 3). Therefore, the functions in new
have replacements in the types
module, such as:
>>> 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'
I couldn’t help but look at the source code of types
. It’s short, but you know… anything involving type
makes my brain go blank.
I still don’t understand what the mechanism is, but another answer Adding a Method to an Existing Object mentioned the descriptor protocol
.
Descriptors are the implementation mechanism behind properties, instance methods, static methods, class methods, and super.
Refer to the Descriptor HowTo Guide and its translation Python Descriptor Guide (Translation).
Also, the ways to dynamically add staticmethod
, classmethod
are:
>>> 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 is also function hello
<function hello at 0x7f39b4bf3758>
>>> a.world # A.world is also bound method classobj.world
<bound method classobj.world of <class __main__.A at 0x7f39b4bf0258>>
So how did I fall into this pit? Actually, it was just to create a whimsical decorator…
This is also the power of dynamic languages:
class AddHigh(object):
""" Add height to a rectangle to turn it into a cuboid """
def __init__(self, cls, z):
self._cls = cls
self._z = z
def __call__(self, *args, **kwargs):
# Rewrite the calculation method of the area
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):
""" Rectangle """
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 -> How to dynamically add a decorator to every server request in python tornado? @Damnever’s answer