
I think the issue that you didn't address is that lexical scoping is a compile-time issue, and that in most languages that variable names that a program uses are a static property of the code. Off the top of my head, I can't think of another lexically scoped language that allows an exec or eval to create a new variable binding that can later be used via a plain-old reference. One of the reasons I am strongly in favor of making import * and exec errors is that it stymies the efforts of a reader to understand the code. Lexical scoping is fairly clear because you can figure out what binding a reference will use by reading the text. (As opposed to dynamic scoping where you have to think about all the possible call stacks in which the function might end up.) With bare exec and import *, the reader of the code doesn't have any obvious indicator of what names are being bound. This is why I consider it bad form and presumably part of the reason that the language references outlaws it. (But not at the module scope, since practicality beats purity.) If we look at your examples: >>> def f(): # x = 3 inside, no g() ... x = 3 ... print x ... from foo import * ... print x ... >>> f() 3 1 >>> def f(): # x = 3 inside, nested g() ... x = 3 ... print x ... from foo import * ... def g(): print x ... g() ... >>> f() 3 1 >>> x = 3 >>> def f(): # x = 3 outside, nested g() ... print x ... from foo import * ... def g(): print x ... g() ... >>> f() 3 1 In these examples, it isn't at all obvious to the reader of the code whether the module foo contains a binding for x or whether the programmer intended to import that name and stomp on the exist definition. Another key difference between Scheme and Python is that in Scheme, each binding operation creates a new scope. The Scheme equivalent of this Python code -- def f(x): y = x + 1 ... y = x + 2 ... -- would presumably be something like this -- (define (f x) (let ((y (+ x 1))) ... (let (y (+ x 2))) ... )) Python is a whole different beast because it supports multiple assignments to a name within a single scope. In Scheme, every binding of a name via lambda introduces a new scope. This is the reason that the example -- x = 3 def f(): print x x = 2 print x -- raises an error rather than printing '3\n2\n'. Jeremy