Dynamically adding and removing methods
Steven D'Aprano
steve at REMOVETHIScyber.com.au
Sun Sep 25 14:37:17 EDT 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