[Tutor] Comments appreciated

Danny Yoo dyoo at hkn.eecs.berkeley.edu
Mon Dec 27 09:25:58 CET 2004



On Sun, 26 Dec 2004, Jacob S. wrote:

> > The only thing that's missing is that this script can't handle paths
> > like ~/dir/junkthis
>
> I believe you're looking for os.path.expanduser("~/dir/junkthis")
>
> BTW, trashcan IS a module level variable because it's defined at the module
> level. Why it says it's local is beyond me.

Hi Jacob,


Ah, you must be running into the global/local gotcha.  This one is
notorious.  Let's work with a few simplified examples to clear up the
situation.


For the sake of simplification, my variable names will suck.  *grin*


Anyway, here's the first example:

###
y = 0
def inc(x):
    y = x + 1
    return y
###

We know that when we assign to variables in functions, those variables are
locally scoped.  This is a fairly unique feature in Python: to assign to a
variable is the same thing as declaration in the Python language.

So in the example above, 'y' in the line:

    y = x + 1

is a local variable, since it's assigned within the function.  And if we
play with this in the interactive interpreter, we'll see that calling
inc() doesn't touch the value of the module-level 'y' variable.

###
>>> y = 0
>>> def inc(x):
...     y = x + 1
...     return y
...
>>> inc(3)
4
>>> y
0
>>> y = 2
>>> inc(7)
8
>>> y
2
###


Hope that made sense so far: assignment in a function causes the assigned
variable to be treated as local to that function.




Now let's look at a slightly different example which highlights the
problem:

###
y = 42

def inc_y():   ## buggy
    y = y + 1
###

The issue is the one brought up by the first example: assignment causes a
variable to be local.  But if 'y' is local, then what does:

    y = y + 1

mean, if 'y' hasn't been assigned to yet?

###
>>> y = 42
>>> def inc_y():
...     y = y + 1
...
>>> inc_y()
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "<stdin>", line 2, in inc_y
UnboundLocalError: local variable 'y' referenced before assignment
###


And this is actually something that could --- and probably should! --- be
handled at "definition" time rather than at "run" time.  We really
shouldn't have to call inc_y() to know that something's already wrong with
it.

[Advanced: In general, it'd be impossible to do it in full generality,
since Python's behavior is so dynamic.  As a hideous example:

###
y = 42
def degenerate_case():
    if some_strange_runtime_condition_was_true():
        y = 42
    y = y + 1
###

Depending on the definition of some_strange_runtime_condition_was_true(),
we'd either die and get a UnboundLocalError, or the function would run to
completion.  But in this case, I would promptly yell at anyone who would
write code like this.  *grin*]


Anyway, to fix the problem, that is, to be able to write a function that
does an assignment to a global, we then have to declare the variable as
'global':

###
y = 42

def inc_y_fixed():
    global y             ## fix for scoping issue
    y = y + 1
###

so that Python knows up front that 'y' in inc_y_fixed is meant to refer to
the module-level 'y'.



All of this is a consequence of Python's approach to variable declaration.
For the common case, we get to save a lot of lines, since we don't have to
write things like

    var x, y, z
    int x;

at the head of every one of our functions.  But, consequently, we do hit
this slightly odd and ugly situation when we want to assign to
module-level variables.



For more information, you may want to look at Tim Peters's nice FAQTS
entry on this subject, linked here:

    http://www.faqts.com/knowledge_base/view.phtml/aid/4722/fid/241


This is also in AMK's "Python Warts" page:

    http://www.amk.ca/python/writing/warts.html

under the header "Local Variable Optimization".


If you have more questions, please feel free to ask.  Hope this helps!



More information about the Tutor mailing list