views:

127

answers:

2

I would like to add some methods to a class definition at runtime. However, when running the following code, I get some surprising (to me) results.

test.py

class klass(object):
    pass

for i in [1,2]:
    def f(self):
     print(i)
    setattr(klass, 'f' + str(i), f)

I get the following when testing on the command line:

>>> import test
>>> k = test.klass()
>>> k.f1()
2
>>> k.f2()
2

Why does k.f1() return 2 instead of 1? It seems rather counter intuitive to me.

notes

This test was done using python3.0 on a kubuntu machine.

A: 

My guess is that it's because print (i) prints i not by value, but by reference. Thus, when leaving the for loop, i has the value 2, which will be printed both times.

Tamás Szelei
I think it's clearer to focus on early vs late binding, as I did in my answer, rather than on "by value" vs "by reference". Both of the solutions I gave are still by reference (can't be otherwise in Python;-) but the way they solve the problem is by forcing an earlier binding of the identifier in either of two ways.
Alex Martelli
+8  A: 

It's the usual problem of binding -- you want early binding for the use of i inside the function and Python is doing late binding for it. You can force the earlier binding this way:

class klass(object):
    pass

for i in [1,2]:
    def f(self, i=i):
        print(i)
    setattr(klass, 'f' + str(i), f)

or by wrapping f into an outer function layer taking i as an argument:

class klass(object):
    pass

def fmaker(i):
    def f(self):
        print(i)
    return f

for i in [1,2]:
    setattr(klass, 'f' + str(i), fmaker(i))
Alex Martelli
Great answer, that definitely solved my problem. Is there somewhere I can go to find the specifics on late vs. early binding? This isn't something I've looked into before.
brad
@brad, http://en.wikipedia.org/wiki/Scope_%28programming%29, http://en.wikipedia.org/wiki/Name_binding, http://en.wikipedia.org/wiki/Closure_%28computer_science%29
Nick D
Good answer, I actually ask something similar when I'm interviewing people
Mark Roddy