Dynamically adding and removing methods

Steven D'Aprano steve at REMOVETHIScyber.com.au
Sat Sep 24 14:41:33 EDT 2005


Suppose I create a class with some methods:

py> class C:
...     def spam(self, x):
...             print "spam " * x
...     def ham(self, x):
...             print "ham * %s" % x
...
py> C().spam(3)
spam spam spam
>>> C().ham(3)
ham * 3

To dynamically remove the methods, delete them from the class like you
would any other attribute:

py> del C.ham
py> C().ham(3)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
AttributeError: C instance has no attribute 'ham'

That's fairly straight forward -- no major surprises there.

How does one dynamically add methods to an instance or class? You might
think you can do this:

py> def eggs(self, x):
...     print "eggs * %s" % x
...
py> inst = C()
py> inst.eggs = eggs
py> inst.eggs(3)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: eggs() takes exactly 2 arguments (1 given)

Even though we've added a function to the instance, it hasn't got all the
machinery to work as a proper method. A work-around is to explicitly pass
an instance:

py> inst.eggs(inst, 3)
eggs * 3

To create a proper method, we do this:

py> import new
py> inst.eggs = new.instancemethod(eggs, None, C)
py> inst.eggs(3)
eggs * 3

You can now delete the top-level function eggs, and the method bound to
inst will not be affected. Because eggs is bound to the instance, new
instances will not understand it:

py> C().eggs(3)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
AttributeError: C instance has no attribute 'eggs'

Or you could put the method in the class and have all instances recognise
it:

py> C.eggs = new.instancemethod(eggs, None, C)
py> C().eggs(3)
eggs * 3

Instead of passing None as the second argument to instancemethod, you can
pass an instance. If you do that, self will automatically be set to that
instance instead of the one doing the calling:

py> def truffles(self, x):
...     print self, x
... 
py> C.truffles = new.instancemethod(truffles, inst, C)
py> inst
<__main__.C instance at 0xf6d2d6cc>
py> inst.truffles(3)
<__main__.C instance at 0xf6d2d6cc> 3

No surprises there. But look what happens when we use a new instance:

py> x = C()
py> x
<__main__.C instance at 0xf6d2ca6c>
>>> x.truffles(3)
<__main__.C instance at 0xf6d2d6cc> 3


Hope this is useful to some folks.


-- 
Steven.




More information about the Python-list mailing list