[Python-Dev] Is outlawing-nested-import-* only an implementation issue?

Jeremy Hylton jeremy@alum.mit.edu
Thu, 22 Feb 2001 22:00:07 -0500 (EST)


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