scoping weirdness

Tim Peters tim.one at home.com
Sun Aug 26 01:41:06 EDT 2001


[Paul Rubin]
> Well ok, but I thought we had a thread the other day saying Python
> doesn't actually have closures :-).

Sure -- there's *always* at least one thread spouting nonsense <0.7 wink>.
Python always had closures, but not always spelled in exactly the way all
newcomers expected (and before nested_scopes, spelled in ways that *no*
newcomer expected -- not even the ones on heavy drugs).

> Anyway I was surprised.

OK, we'll take nested scopes out again -- they were a bloody nuisance to
explain to non-Scheme-heads anyway <wink>.

> ...
> Yes, let me go up a level and ask if there's a better way to do this.
> It's for Tkinter--say I want several buttons that all call the same
> function, giving the button text as an argument.  My code looks
> something like:
>
>     from __future__ import nested_scopes
>     from Tkinter import *
>
>     def press_button (text):
>       print 'you pressed %s' % text
>
>     def create_button (label):
>       func = lambda: press_button (label)
>       Button(root,text=label,command=func).pack()
>       # if I put more stuff here and set label to something new,
>       # the button doesn't do what I expected.
>
>     root = Tk()
>     create_button ("fear")
>     create_button ("surprise")
>     create_button ("ruthless efficiency")

Pick one:

1. If you don't want label to change, *don't* set label to something new.
   If you poke yourself in the eye, it will hurt.

2. Exploit the behavior you disliked last week (and this is one of the
   reasons it was deliberately done this way):

        def func(label=label):
            return press_button(label)

   That is, exploit that default arguments capture their values just
   once, at function definition time, rather than anew at each invocation.
   (BTW, you can use a lambda there instead; I simply prefer not to, as
   I find named defs clearer, and easier to change over time.)

3. Write a class.

class ButtonPresser:
    def __init__(self, label):
        self.label = label

    def __call__(self):
        return press_button(self.label)

...
    func = ButtonPresser(label)

4. Write another class.

class ButtonPresser:
    def __init__(self, label):
        self.label = label

    def worker(self):
        return press_button(self.label)

...
    func = ButtonPresser(label).worker

5: Go insane:
    func = eval("lambda: push_button(%r)" % label)

> The point is that from the Tkinter docs it looks like the Button
> widget really wants its command parameter to be a function with no
> args, so I needed to create a lambda that would call press_button with
> the arg I wanted.

More accurately, it needs to be a callable object that can tolerate being
called with no arguments.  "callable objects" includes possibilities like #3
and #4 above, creating an instance that defines __call__ (#3), or a bound
method object (#4).  #3/#4 are overkill in this case, but grow more
attractive the more state you want to associate with a button.

> What would be best is if the Button widget had a way of passing args
> to its command func.  Is there something like that which I missed?

I don't know.  But it's Python:  if that's what you like, subclass Button
and build in the behaviors *you* like best.  It's easy to extend #3 to a
general wrapper class that can curry away any kind of argument list.  BTW, I
think it's been proven that GUI programming is the one thing OO is actually
good for <wink>.





More information about the Python-list mailing list