Dec
14
Python strangeness
Could someone explain this?
Code:
list = (
(0, 'foo'),
(1, 'bar'),
(2, 'baz'),
)
class Klass:
def bat(self):
print '-1'
for l in list:
if not hasattr(Klass, l[1]):
print '%s should print %d' % (l[1], l[0])
def _f(self):
print l[0]
_f.__doc__ = 'Get %s' % l[1]
setattr(Klass, l[1], _f)
k = Klass()
print
print 'Docstrings:'
print 'Foo:', k.foo.__doc__
print 'Bar:', k.bar.__doc__
print 'Baz:', k.baz.__doc__
print
print 'Executed:'
print 'bat:',
k.bat()
print 'foo:',
k.foo()
print 'bar:',
k.bar()
print 'baz:',
k.baz()
print
print 'Locations:'
print 'bat:', str(k.bat.im_func)
print 'foo:', str(k.foo.im_func)
print 'bar:', str(k.bar.im_func)
print 'baz:', str(k.baz.im_func)
Output:
foo should print 0 bar should print 1 baz should print 2 Docstrings: Foo: Get foo Bar: Get bar Baz: Get baz Executed: bat: -1 foo: 2 bar: 2 baz: 2 Locations: bat: <function bat at 0xb7c58e2c> foo: <function _f at 0xb7c58df4> bar: <function _f at 0xb7c58f0c> baz: <function _f at 0xb7c58f44>
$ python -V Python 2.4.4
Maybe I'm just missing something... Don't flame ;-)
Comments:
(Note: the code in this comment uses > for indentation, because you don't allow the pre tag.)
The problem comes when you define the function _f. Your definition references l by reference, not by value. Your loop changes l.
This way of binding variables from outer scopes provides a lot of power; for example, you can define a callback function that references variables from your outer scope directly, rather than needing a magic "caller context" argument as you might find in C.
To do what you want, try this:
Alternatively, reference the original tuple item, which won't change:
As an aside, don't call your lists
has a built-in function named
The problem comes when you define the function _f. Your definition references l by reference, not by value. Your loop changes l.
This way of binding variables from outer scopes provides a lot of power; for example, you can define a callback function that references variables from your outer scope directly, rather than needing a magic "caller context" argument as you might find in C.
To do what you want, try this:
def make_print(v, doc):
>def _f():
>>print v
>_f.__doc__ = doc
>return f
for l in list:
>if not hasattr(Klass, l[1]):
>>print '%s should print %d' % (l[1], l[0])
>>setattr(Klass, l[1], make_print(l[0], 'Get %s' % l[1]))
Alternatively, reference the original tuple item, which won't change:
for i in enumerate(list):
>if not hasattr(Klass, list[i][1]):
>>print '%s should print %d' % (list[i][1], list[i][0])
>>def _f(self):
>>>print list[i][0]
>>_f.__doc__ = 'Get %s' % list[i][1]
>>setattr(Klass, list[i][1], _f)
As an aside, don't call your lists
list
, because Pythonhas a built-in function named
list
. Worse yet, don't call your tuples list
. :)
Permalink
12/14/07 @ 02:50
Comment from: Ikke [Member] · http://www.eikke.com
Here's how I was thinking: "list" is an array of pointers to some "tupple" structure. I iterate through all these pointers, and assign the location of the current tupple to "l". Then I generate some function, _f, which uses the object at location given by l. If later on I change l (so it points to something else), the memory location used into the previously defined function should not change *with* l... It's like
char *foo = "bar";
char *baz = foo;
foo = "bat";
printf("%s %s\n", foo, baz);
which prints "bat bar", not "bat bat".
I know about the "list" name, stupid, wouldn't do that in production code :-)
char *foo = "bar";
char *baz = foo;
foo = "bat";
printf("%s %s\n", foo, baz);
which prints "bat bar", not "bat bat".
I know about the "list" name, stupid, wouldn't do that in production code :-)
Permalink
12/14/07 @ 03:08
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/502271
Permalink
12/14/07 @ 04:04
Comment from: James Henstridge [Visitor] · http://blogs.gnome.org/jamesh/
One thing you need to remember is that the nested function _f is not storing a reference to "l" from the parent scope: it has a reference to the parent scope itself. So when you rebind "l" in the parent scope, it affects what _f() sees.
There are a few solutions to the problem:
1. bind "l" as a default value to an argument of _f().
2. define _f() in a scope where "l" does not change (i.e. call another function that creates _f() inside your loop).
3. Go the object oriented route, and use an instance rather than a function. By making use of the descriptor __get__() call, you can do method-style binding to instances.
There are a few solutions to the problem:
1. bind "l" as a default value to an argument of _f().
2. define _f() in a scope where "l" does not change (i.e. call another function that creates _f() inside your loop).
3. Go the object oriented route, and use an instance rather than a function. By making use of the descriptor __get__() call, you can do method-style binding to instances.
Permalink
12/14/07 @ 04:25
In Python, closure capture the current frame (a reference to it in fact), not the current binding. So in when you define the local _f function, the current frame is captured, and when it is called, the local variable l is searched in this frame, and since the frame is not copied, but merely referenced, the value is the last value bound to the name l in the frame.
A simple example :
If you want to capture the current value, you need to change your function to take an additional parameter with a default value initialized with l[0], as default value of parameter are evaluated when compiling the function.
Your code should be changed to (only including the function declaration) :
A simple example :
>>> def test():
... def f():
... print i
... i = 4
... f()
... i = 5
... f()
...
>>> test()
4
5
If you want to capture the current value, you need to change your function to take an additional parameter with a default value initialized with l[0], as default value of parameter are evaluated when compiling the function.
Your code should be changed to (only including the function declaration) :
>>> for l in list:
... if not hasattr(Klass, l[1]):
... print '%s should print %d' % (l[1], l[0])
... def _f(self, v = l[0]):
... print v
... _f.__doc__ = 'Get %s' % l[1]
... setattr(Klass, l[1], _f)
Permalink
12/14/07 @ 08:13