2005-11-23

Python global stupidity

Consider the following seemingly trivial piece of python code, directly pasted from the interpreter:

>>> x=0
>>> def f():
... x+=3
... print x
...
>>> f()
Traceback (most recent call last):
File "", line 1, in ?
File "", line 2, in f
UnboundLocalError: local variable 'x' referenced before assignment
Yes, thank you, I know that global variables are "evil". I don't care when I'm writing a throw-away program.

My biggest objection to python while I was learning it was strange and unintuitive rule about scoping. And even now, when I'm pretty experienced in Python, I get bitten by it sometimes. Here's how to fix the upper piece of code: add global x just before the assignment addition. Strangely, the following piece of code works:

>>> def g():
... print x
...
>>> g()
0
Personally, I think that this is both unexpected and broken behaviour. This is not how lexical scoping (present in all modern programming languages) works. And the more I follow on the python development, the more hackish it is becoming.

If you want a nice, small, and elegant language to embed in your application for scripting, take a look at Lua.

3 comments:

Anonymous said...

You obviously have wrong perception of Python scoping rules.

x = 0
def f():
print x

When you call f(), the interpreter creates new namespace for local variables. When you try to print ‘x’, the interpreter looks at that local namespace for variable x definition. As it cannot find it there, it looks at __parent__ object namespace. Interpreter finds that ‘x’ is defined[*] in that namespace so it prints it’s value.

x = 0
def f():
x += 3
print x

First, local namespace is created. When you try to add number 3 to variable ‘x’, the interpreter expects that ‘x’ is defined in local namespace but it is not. You must use ‘global’ keyword here to instruct interpreter to look at __parent__ namespace.

[*] Note that word ‘definition’ has no meaning in Python as in other languages

zvrba said...

Actually, I have a wrong perception of +=. I read x+=3 the equivalent of x=x+3.

Now, following your description the x on the RHS of x=x+3 should be looked up in the parent scope (where it is defined as 0), and the result assigned to either the global x (the "expected" thing to do) or the new local copy of x (the "unexpected" thing to do).

So, can you explain in what fundamental way is x=x+3 different from print x or, even better example, y=x+3? In all cases the RHS is fully defined, however in the x=x+3 case the interpreter complains.

The only explanation that I can find is that, immediately upon entry to f, the interpreter knows ALL local variables that are used in f so binds them all to uninitialized values just after the opening : of the function definition.

In any case, I think that this is broken behaviour that doesn't deserve the name of nested scopes or static scoping.

Anonymous said...

zvrba: I think your last interpretation is correct, and I don't think it is that outlandish.

In python,

var = expression

is a definition of a local variable. Python could have had us writing something like:

define var = expression

for the first assignment to a variable, but I think I'd rather have the current, compact notation.

It isn't hard to see if a variable is being defined, just look if it's being assigned (whether through = or +=).

Right?