The absolute statement is false.

Dynamically Add Methods to Existing Python Classes

(NOTE: this article includes content translated by a machine)

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