Ikke's Blog

Post details: Python strangeness

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:

Comment from: Anonymous [Visitor] Email
(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:

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 Python
has a built-in function named list. Worse yet, don't call your tuples list. :)
PermalinkPermalink 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 :-)
PermalinkPermalink 12/14/07 @ 03:08
Comment from: ignacio [Visitor] Email
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/502271
PermalinkPermalink 12/14/07 @ 04:04
Comment from: James Henstridge [Visitor] Email · 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.
PermalinkPermalink 12/14/07 @ 04:25
Comment from: Sylvain Defresne [Visitor] Email
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 :


>>> 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)
PermalinkPermalink 12/14/07 @ 08:13

Leave a comment:

Your email address will not be displayed on this site.
Your URL will be displayed.

Allowed XHTML tags: <p, ul, ol, li, dl, dt, dd, address, blockquote, ins, del, span, bdo, br, em, strong, dfn, code, samp, kdb, var, cite, abbr, acronym, q, sub, sup, tt, i, b, big, small>
(Line breaks become <br />)
(Set cookies for name, email and url)
(Allow users to contact you through a message form (your email will NOT be displayed.))

Categories

Who's Online?

  • Guest Users: 14

Misc

XML Feeds

What is RSS?