Steven D'Aprano wrote: > 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.
No. Look at your eggs function. It takes two arguments. So the function is not modified at all. (Perhaps you expected it to be?) > 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. Again, look at your eggs function. It takes two arguments. So you got exactly the same object back. Testing this: py> class Klass: ... pass ... py> def eggs(self, x): ... print "eggs * %s" % x ... py> inst = Klass() py> inst.eggs = eggs py> neweggs = inst.eggs py> eggs is neweggs True So you get back exactly what you previously assigned. Note that it's actually with *classes*, not *instances* that you don't get back what you set: py> Klass.eggs = eggs py> Klass.eggs <unbound method Klass.eggs> py> Klass.eggs is eggs False > 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. With classes, the descriptor machinery is invoked: py> Klass.eggs <unbound method Klass.eggs> py> Klass.eggs.__get__(None, Klass) <unbound method Klass.eggs> py> Klass.eggs.__get__(Klass(), Klass) <bound method Klass.eggs of <__main__.Klass instance at 0x01290BC0>> Because instances do not invoke the descriptor machinery, you get a different result: py> inst.eggs <function eggs at 0x0126EBB0> However, you can manually invoke the descriptor machinery if that's what you really want: py> inst.eggs.__get__(None, Klass) <unbound method Klass.eggs> py> inst.eggs.__get__(inst, Klass) <bound method Klass.eggs of <__main__.Klass instance at 0x012946E8>> py> inst.eggs.__get__(inst, Klass)(1) eggs * 1 Yes, the behavior of functions that are attributes of classes is different from the behavior of functions that are attributes of instances. But I'm not sure I'd say that it's broken. It's a direct result of the fact that classes are the only things that implicitly invoke the descriptor machinery. Note that if instances invoked the descriptor machinery, setting a function as an attribute of an instance would mean you'd always get bound methods back. So code like the following would break: py> class C(object): ... pass ... py> def f(x): ... print 'f(%s)' % x ... py> def g(obj): ... obj.f('g') ... py> c = C() py> c.f = f py> g(c) f(g) If instances invoked the descriptor machinery, "obj.f" would return a bound method of the "c" instance, where "x" in the "f" function was bound to the "c" object. Thus the call to "obj.f" would result in: py> g(c) Traceback (most recent call last): File "<interactive input>", line 1, in ? File "<interactive input>", line 2, in g TypeError: f() takes exactly 1 argument (2 given) Not that I'm claiming I write code like this. ;) But I'd be hesitant to call it broken. STeVe -- http://mail.python.org/mailman/listinfo/python-list