[Tutor] variable existence q

Steven D'Aprano steve at pearwood.info
Sun Aug 16 11:24:15 CEST 2015

On Sat, Aug 15, 2015 at 03:38:31PM -0700, Clayton Kirkwood wrote:
> top_directory = "/users/Clayton/Pictures"
> target_directory = top_directory      #directory we are checking
> filetypes = ('jpg', 'png', 'avi', 'mp4', 'mov', 'bmp')
> imports...
> def override_defaults():
>     with open( user_preferences ) as f:
>         for line in f.readline():
>             llist = line.split()
>             if llist[0] == '#':   #comment line to ignore
>                 continue
>             elif llist[0] == 'top_directory':
>                 if len(llist) == 1:
>                     pass
>                 else:
>                     top_directory = llist[1]

This line tells the compiler that top_directory must be a local 
variable, since you assign to it within a function. As a local variable, 
it only gets set on *some* paths through the function, so you get an 
error, same as this:

def example(n):
    if n % 2 == 0:
        td = "test"
    return td  # fails half the time

Python uses a simple rule to decide whether or not a variable is a local 
or not. If the variable is assigned to *anywhere* in the function, it is 
treated as local. Even if the line is never actually executed! (For this 
purpose, "del" is treated as a de facto assignment too.) So you can even 
do this:

x = 23

def test():
    return x
    # code below here is never executed
    if False:
        # and even if it were, code inside this block is never executed
        x = 42  # makes x a local variable

and calling test() will now give an UnboundLocalError.

To tell Python not to treat it as a local, you need to declare it 
global. Same with target_directory. So one solution is to put this as 
the first line of your function:

    global top_directory, target_directory

(Technically, you can put it anywhere inside the function, yes, even 
after the return statement, and it will have the same effect. But don't 
do that. Always put it at the start.)

Another solution is to write the function like this:

def override_defaults():
    top = top_directory
    target = target_directory
    with open( user_preferences ) as f:
        for line in f:  # no need for f.readlines
            line = line.strip()  # ignore leading and trailing whitespace
            words = line.split()
            if words[0].startswith('#'):   #comment line to ignore
            elif words[0] == 'top_directory':
                top = words[1]
        [ ... ]
    return (top, file_types, target)


More information about the Tutor mailing list