Dynamically adding and removing methods

Steven D'Aprano steve at REMOVETHIScyber.com.au
Sun Sep 25 20:37:17 CEST 2005


On Sun, 25 Sep 2005 14:52:56 +0000, Ron Adam wrote:

> Steven D'Aprano wrote:
> 
> 
>> 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
> 
> Why not just add it to the class directly?  You just have to be sure 
> it's a class and not an instance of a class.

Because I started off explicitly adding functions to instances directly,
and when I discovered that didn't work properly, I never even tried adding
it to the class until after I discovered that instancemethod() worked.

As far as I can see, Python's treatment of functions when you dynamically
add them to classes and instances is rather confused. See, for example:

py> class Klass:
...     pass
...
py> def eggs(self, x):
...     print "eggs * %s" % x
...
py> inst = Klass()  # Create a class instance.
py> inst.eggs = eggs  # Dynamically add a function/method.
py> inst.eggs(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: eggs() takes exactly 2 arguments (1 given)

>From this, I can conclude that when you assign the function to the
instance attribute, it gets modified to take two arguments instead of one.
Test it by explicitly passing an instance:

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

My hypothesis is confirmed.

Can we get the unmodified function back again?

py> neweggs = inst.eggs
py> neweggs(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: eggs() takes exactly 2 arguments (1 given)

Nope. That is a gotcha. Storing a function object as an attribute, then
retrieving it, doesn't give you back the original object again. 

So while you can do this:

def printgraph(f):  # print a graph of a function
    parameters = get_params()
    draw_graph(f, parameters)

you can't do this:

def printgraph(function):  # print a graph of a function
    parameters = get_params()
    parameters.function = f  # WARNING: f is modified here
    draw_graph(parameters)



When storing the function object as an instance object, it is
half-converted to a method: even though eggs is modified to expect two
arguments, Python doesn't know enough to automatically pass the instance
object as the first argument like it does when you call a true instance
method.

Furthermore, the type of the attribute isn't changed:

py> type(eggs)
<type 'function'>
py> type(inst.eggs)
<type 'function'>

But if you assign a class attribute to a function, the type changes, and
Python knows to pass the instance object:

py> Klass.eggs = eggs
py> inst2 = Klass()
py> type(inst2.eggs)
<type 'instancemethod'>
py> inst2.eggs(1)
eggs * 1

The different behaviour between adding a function to a class and an
instance is an inconsistency. The class behaviour is useful, the instance
behaviour is broken.


>  >>> def beacon(self, x):
> ...    print "beacon + %s" % x
> ...

Did you mean bacon? *wink*

>  >>> C.beacon = beacon
>  >>> dir(A)
> ['__doc__', '__module__', 'beacon', 'ham', 'spam']

Okay, you aren't showing all your code. What is A?


-- 
Steven.




More information about the Python-list mailing list