scoping weirdness

Tim Peters tim.one at home.com
Sat Aug 25 08:56:43 CEST 2001


[Paul Rubin]
>     from __future__ import nested_scopes   # python 2.1.1
>
>     def foo (n):
>         a = lambda: 'you said (%d)' % n
>         n += 3
>         return a
>
>     x = foo(1)
>     print x()
>
> This prints 4, rather than 1 as I would have expected.
> I think I understand what's going on, but is it really what was intended?

Of course.  Try the same thing in Scheme (which you've already confessed to
knowing <wink>):

(define (foo n)
  (let ((a (lambda () n)))
    (set! n (+ n 3))
    a))

(define x (foo 1))

After that, (x) evaluates to 4 too.  Same thing:  lambdas aren't macros
expanded at definition time, the "n" in the body is an uplevel reference to
the current binding of n in the enclosing scope at the time the lambda body
is *executed*.  In your case (and in the Scheme rewrite), n is rebound to 4
before the body is executed, so 4 is what the body sees.

> Is there a preferred way of writing this kind of function?

Ya, use Scheme <0.6 wink>.  If you want to capture a vrbl's value at
function *definition* time, you could, e.g., abuse default arguments, a la

    a = lambda n=n: 'you said (%d)' % n

Or you could do any of the things you'd do in Scheme, most obviously just
refraining from rebinding n in the enclosing scope.

Most Python programmers would write a class, though, explicitly binding an
instance variable to the value they wanted at the time they wanted to
capture it.  Like:

class Whatever:
    def __init__(self, n):
        self.n = n
        n += 3

    def __call__(self):
        return 'you said (%d)' % self.n

x = Whatever(1)
print x()

That prints

    you said (1)

It's a bit more typing at the start, but more flexible in the end, because
the state is materialized explicitly (and so open to fiddling via writing
new methods as requirements change), rather than implicitly hidden away in
closures.





More information about the Python-list mailing list