1

I am going crazy. This simple callback function doesn't recognize a variable assigned right before it in it's parent scope. I get a local variable 'elapsed' referenced before assignment error`. Why?

total = os.path.getsize(filename)
elapsed = 0

def progress_print(chunk):
    elapsed += len(chunk)  # elapsed is apparently unassigned??
    percent = float(elapsed) / float(total) * 100
    print '  Copied %s%%\033[A' % int(percent)

ftp.upload(filename, temp_path, callback=progress_print)
MFB
  • 19,017
  • 27
  • 72
  • 118

2 Answers2

4

You're trying to assign to a global. Python makes you explicitly do that.

def progress_print(chunk):
    global elapsed
    elapsed += len(chunk)

In general this is a pretty good sign that you need to refactor your code until you don't need globals any more.

Note that assignment and access are different beasts - the former requires you to explicitly declare globals, the latter does not. Case in point, your total variable is another global but you do not attempt to assign to it, hence python does not complain.

The Programming FAQ goes into more detail:

What are the rules for local and global variables in Python?

In Python, variables that are only referenced inside a function are implicitly global. If a variable is assigned a new value anywhere within the function’s body, it’s assumed to be a local. If a variable is ever assigned a new value inside the function, the variable is implicitly local, and you need to explicitly declare it as ‘global’.

Though a bit surprising at first, a moment’s consideration explains this. On one hand, requiring global for assigned variables provides a bar against unintended side-effects. On the other hand, if global was required for all global references, you’d be using global all the time. You’d have to declare as global every reference to a built-in function or to a component of an imported module. This clutter would defeat the usefulness of the global declaration for identifying side-effects.

roippi
  • 25,533
  • 4
  • 48
  • 73
  • I don't see why it needs to be a global. The `total` variable is not a global and it is referenced within the callback function without any problem. I don't get it :( – MFB Apr 23 '14 at 00:54
  • your `total` variable **is** a global, but you only access it, you don't attempt to assign to it. That's the difference. Only assignment requires explicit global declaration. – roippi Apr 23 '14 at 00:57
  • Ok, that makes sense. However, if I declare `elapsed` as a global within the callback function, then `+=` will always yield the same result, right? – MFB Apr 23 '14 at 01:00
  • I don't know what you mean by "same result." – roippi Apr 23 '14 at 01:01
  • @roippi: He would run into this problem even without globals by the way, i.e. a nested function referencing a variable in the parent function scope. – Erik Kaplun Apr 23 '14 at 01:20
  • Never mind, I figured it out.. Thanks very much for the great answer. – MFB Apr 23 '14 at 01:20
0

When you do a

def progress_print(chunk):
    elapsed += len(chunk)

Here elapsed is the new local variable. Since you are doing a +=, which is nothing but:

elapsed = elapsed + len(chunk)  

Here, elapsed is not initialized with any value, that is why you get the error - referenced before assignment.

Hence you need to do:

def progress_print(chunk):
    global elapsed
    elapsed += len(chunk)
doubleo
  • 4,389
  • 4
  • 16
  • 19