Scoping "bug" ?

Alex Martelli alex at magenta.com
Fri Aug 18 12:26:06 EDT 2000


"Kevin Bailey" <noone at nowhere.com> wrote in message
news:399D50FE.A2708ED9 at nowhere.com...
> I get the feeling that this isn't a bug

Right, it isn't: the local-or-global state of a name in
a function is, *by design*, determined *at compile time*.

> but I'd be quite disappointed
> if this is intended behavior:
>
> Python 1.5.2 (#2, Jun  9 2000, 16:02:51)  [GCC 2.95.2 19991024 (release)]
> Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
> >>> a = 1
> >>> def fun1(x):
> ...     if x:
> ...             print a
> ...
> >>> def fun2(x):
> ...     if x:
> ...             print a
> ...     else:
> ...             a = 2
> ...
> >>> fun1(1)
> 1
> >>> fun2(1)
> Traceback (innermost last):
>   File "<stdin>", line 1, in ?
>   File "<stdin>", line 3, in fun2
> NameError: a
> >>>
>
> In case it's not obvious, the exact same code 'if x: print a' does
> different things in these functions simply because of the presence of
> some _later_ code which may _never_ execute (and certainly _doesn't_
> execute whenever 'print a' does.) This is completely non-intuitive
> behavior. It is as though 'a' gets "half-way" defined in function 'fun2'.

To be more specific: a, being bound in some place in the code of
the function, and never declared as global in the function, is
'defined' *as a local variable* by the Python *compiler* -- when
the function is *compiled*.

The *local variable* a of this function is unbound when the 'print a'
is executed, whence the error.

> It gets defined enough for a reference to it to match locally
> before matching globally yet it doesn't get defined enough to avoid the
> NameError. We have a new level of variable definition now ? Something
> akin to Shreodinger's cat ?

I do not think 'definition' of a variable is a Python concept.

Rather, there are *namespaces* (three of them at any moment: local,
global, built-in) and *bindings* (of a name to a value in a
namespace).

'NameError' means: this name is not bound (in the namespace[s]
in which it's searched for).

The determination of what namespace[s] to look up a binding in
is done at compile-time.  The actual look up is done at run-time.


> Best case is that this is intended to avoid errors. That is, if the
> interpretter executes the 'print a' code, you'll discover a scoping
> problem. This is insufficient, however, because if the 'a = 2'
> code only ever executes, you have bug and you'd be hard pressed to
> find it.

If a binding to a local variable is executed, the local variable
gets bound.  This may or may not be a bug, just like, say, adding
7 to x may or may not be a bug; it's definitely not something that
Python goes out of its way to warn you against.


> But what's really wrong is that Python has unexpected scoping rules
> when it doesn't need to have them. I thought the whole point of
> Python was to be able to teach programming without having to stop
> and teach a lot of exceptions. Instead of:

I do not believe "the WHOLE point" of Python is about teaching
programming.  Rather, the pleasant absence of exceptions has, as
one of several pleasant side effects, that of making it usable
for this purpose as well as others.


> if the variable is defined locally, use it else
> if the variable is defined in the module, use it else
> ...
>
> its now:
>
> if the variable is defined locally, use it unless
> the variable "may" be defined locally in which case behave
> in a totally strange manner unlike any other language ...

Not really -- wrong framing.  I would frame it otherwise:

-- if a name is declared to be global, look it up
    in the global namespace; else,
-- if a name is bound somewhere in this procedure,
    look it up in the local namespace; else,
-- look it up in the global namespace.

[Each lookup in global namespace implies a further lookup
in the builtin namespace if it fails; but local namespace
never implies any further lookup if a lookup there fails;
so "global namespace" always stands for "global-or-builtin
namespace[s]", while "local namespace" stands for itself].

Doing otherwise, i.e. using _dynamic_ determination of
what namespace is involved, depending on what bindings
happen to have been executed, has been tried in the past
(e.g., in the original LISP), and mostly abandoned, as it
causes far too many problems (modern LISP's, such as
Common Lisp and Scheme, use lexical binding, not dynamic).

(Using an unlimited number of nested namespaces is very
popular among programming languages, but I, personally,
prefer the simpler, 3-namespace approach of Python; this,
however, is a different matter -- how many 'active'
spaces are there, rather than 'which one' to use for
a given lookup).


> If the goal here is to avoid errors, the interpretter should spit
> out a warning instead of perverting the scoping rules. Something

There is no perversion of scoping rules: the three very
simple rules above-mentioned ARE the scoping rules of the
Python language.  What namespace is used to lookup a given
name in never depends on actual/dynamic execution flow: it
can always be determined statically, and lexically, from
program-text alone.  _Whether the lookup will succeed or
fail_ is a completely separate issue, which is only found
out at runtime (and failure causes a NameError exception).


> like 'global variable used in same function as it is defined locally'
> should raise eyebrows.
>
> Someone please tell me this is just a bug.

Sorry, it's definitely the way Python was designed -- and I,
personally, think it's a very workable approach.  If you get
used to thinking of the '3 namespaces', the staticity of the
determination of which one to use for the lookup of a given
name in a certain function, and the fact that builtin shadows
the global but never the local namespace, I think you might
come to agree about the simplicity and effectiveness of this.

By the way, local-namespace lookups are quite fast, faster by
far than global-else-builtin-namespace lookups; an interesting
and pleasant effect the compiler can achieve because of this.


Alex






More information about the Python-list mailing list