Mindboggling Scope Issue

Steve Holden steve at holdenweb.com
Sun Oct 24 20:23:48 EDT 2004


James Stroud wrote:
> Hello All,
> 
> Please bare with me as I have seldom asked for help with my code, so I may do 
> things a little wrong at times. If I ask in the wrong way, please by friendly 
> like Tarry Reedy and suggest how I may better ask my questions.
> 
> So I will retry this one from the top, and with Tarry's suggestions 
> incorporated. 
> 
That's great!

> First I'll begin with a simple example that works from the python CLI:
> 
> % python
> Python 2.3.3 (#2, Feb 17 2004, 11:45:40)
> [GCC 3.3.2 (Mandrake Linux 10.0 3.3.2-6mdk)] on linux2
> Type "help", "copyright", "credits" or "license" for more information.
> 
>>>>def outer():
> 
> ...   def inner():
> ...     print bob
> ...   bob = "wuzzup?"
> ...   inner()
> ...
> 
>>>>outer()
> 
> wuzzup?
> 
> This is an experiment to prove that a function can "de-reference" a name even 
> if the function is defined in a position of the code that preceeds said 
> name's assignment in the enclosing scope. (Hopefully my vocabulary makes 
> sense here--I am not a computer scientist). Please look at the code to see 
> what I mean. Here, we are talking about the name "bob".
> 
Yes, variable references in the function definition are only 
de-referenced when the function is executed. As long as bob has a value 
when inner() is called everything is OK.

Having said that, using global variables (or any variable in an outer 
scope) intoduces couplings in your code which make it much less easy to 
maintain. It's sometimes unavoidable, but the ideal is to have all 
inputs as function parameters and all results returned byt he function.

> Second I present below two alternatives (designated "method1" and "method2" in 
> the comments of the listings below) to a method that resides in a program I 
> am writing. I run the program with the same python executable with which I 
> generated the above example. These method listings are long but differ only 
> where I have indicated in the comments. I am including them in their entirety 
> so that I don't leave something critical out that I, as a relative novice, am 
> missing.
> 
> With "method1" I get no error and the "ok()" method works as I expect and in 
> accord with the example above. With "method2" I call "ok()", and when it 
> prints to stdout I get the following error (after compile, when the program 
> is being run):
> 
> Exception in Tkinter callback
> Traceback (most recent call last):
>   File "/usr/lib/python2.3/lib-tk/Tkinter.py", line 1345, in __call__
>     return self.func(*args)
>   File "./passerby", line 1378, in ok
>     print result
> UnboundLocalError: local variable 'result' referenced before assignment
> 
Excellent. Because the PassWindow object is (presumably) defined in the 
top-level scope of the module it can be referenced from inside the 
nested function definitions as well as the outer function definitions. 
Because what's being referenced is an attribute of a globally-available 
object, everything works.

The problem arises with the "result" version because the inner functions 
contain assignments to the "result" variable. The compiler therefore 
assumes that they are local.

> So my question is, what is special about what I am doing here? Why does it not 
> work like the first example I gave with the name bob? Have I found a bug in 
> python? I should note that "passWindow" is not defined anywhere else but this 
> method. Also, I should note that "method1" represents a 
> workaround/alternative, so my program runs just fine. I just want to know 
> what is going on here to imporve my own understanding. I think if anyone can 
> give me a satisfactory answer on this one, we should all call them a "real 
> expert".
> 
> Here are the listings I referred to above:
> 
> -----
> 
>     # method1 : works!
>     def ask_for_password(self, message):
>       def cancel():
>         passWindow.destroy()
>       def ok():
>         # different : WORKS FINE!
>         print passWindow.result
>         pw = passEntry.get()
>         if len(pw):
>           # different
>           passWindow.result = pw
>         passWindow.destroy()
>       message = "\n" + message + "\n"
>       passWindow = Toplevel(self.get_mainWindow())
>       # different : notice that passWindow is defined for the
>       #                 first time in this whole program in the line above
>       #                 It is not part of a larger scope!
>       passWindow.result = None
>       passWindow.title("passerby - Password Entry")
>       self.get_mainWindow().deiconify()
>       passWindow.transient(self.get_mainWindow())
>       passWindow.geometry("400x180+50+50")
>       passWindow.resizable(0,0)
>       passLabel = Label(passWindow, text=message)
>       passEntry = Entry(passWindow)
>       passEntry.configure(width=48)
>       passEntry.config(show="*")
>       passEntry.bind("<Return>",ok)
>       cancelButton = Button(passWindow, text="Cancel", command=cancel)
>       okButton = Button(passWindow, text="OK", command=ok)
>       passLabel.pack()
>       passEntry.pack()
>       cancelButton.pack()
>       okButton.pack()
>       passEntry.focus_set()
>       passWindow.grab_set()
>       self.get_tk().wait_window(passWindow)
>       # different
>       return passWindow.result
> 
> -----
> 
>     # method2 : doesn't work!
>     def ask_for_password(self, message):
>       def cancel():
>         passWindow.destroy()
>       def ok():
>         # different : GETS ERROR!
>         print result
>         pw = passEntry.get()
>         if len(pw):
>           passWindow.result = pw
>         passWindow.destroy()
>       message = "\n" + message + "\n"
>       passWindow = Toplevel(self.get_mainWindow())
>       # different : notice that passWindow is defined for the
>       #                 first time in this whole program in the line above
>       #                 It is not part of a larger scope!
>       result = None
>       passWindow.title("passerby - Password Entry")
>       self.get_mainWindow().deiconify()
>       passWindow.transient(self.get_mainWindow())
>       passWindow.geometry("400x180+50+50")
>       passWindow.resizable(0,0)
>       passLabel = Label(passWindow, text=message)
>       passEntry = Entry(passWindow)
>       passEntry.configure(width=48)
>       passEntry.config(show="*")
>       passEntry.bind("<Return>",ok)
>       cancelButton = Button(passWindow, text="Cancel", command=cancel)
>       okButton = Button(passWindow, text="OK", command=ok)
>       passLabel.pack()
>       passEntry.pack()
>       cancelButton.pack()
>       okButton.pack()
>       passEntry.focus_set()
>       passWindow.grab_set()
>       self.get_tk().wait_window(passWindow)
>       # different
>       return result
> 
> 
> 
The structure of your code could perhaps be better-ordered. I don't know 
whether you've seen much other Tkinter code, but there are fairly 
well-defined guidelines for how to proceed, and they certainly helped me.

You might want to take a look at some of the following links to help you 
to improve your coding skills:

http://www.pythonware.com/library/tkinter/introduction/index.htm
(by Fredrik Lundh, a well-known Python programmer who has contributed 
extensively to the Tkinter implementation).

http://www.manning.com/catalog/view.php?book=grayson&item=source
(Code samples from the book "Python and Tkinter Programming")

regards
  Steve
-- 
http://www.holdenweb.com
http://pydish.holdenweb.com
Holden Web LLC +1 800 494 3119



More information about the Python-list mailing list