views:

166

answers:

3

I have code like this:

def write_postcodes(self):
    """Write postcodes database. Write data to file pointer. Data 
    is ordered. Initially index pages are written, grouping postcodes by
    the first three characters, allowing for faster searching."""
    status("POSTCODE", "Preparing to sort...", 0, 1)
    # This function returns the key of x whilst updating the displayed 
    # status of the sort.
    ctr = 0
    def keyfunc(x):
        ctr += 1
        status("POSTCODE", "Sorting postcodes", ctr, len(self.postcodes))
        return x
    sort_res = self.postcodes[:]
    sort_res.sort(key=keyfunc)

But ctr responds with a NameError:

Traceback (most recent call last):
  File "PostcodeWriter.py", line 53, in <module>
    w.write_postcodes()
  File "PostcodeWriter.py", line 47, in write_postcodes
    sort_res.sort(key=keyfunc)
  File "PostcodeWriter.py", line 43, in keyfunc
    ctr += 1
UnboundLocalError: local variable 'ctr' referenced before assignment

How can I fix this? I thought nester scopes would have allowed me to do this. I've tried with 'global', but it still doesn't work.

+3  A: 

From http://www.devshed.com/c/a/Python/Nested-Functions-in-Python/1/

Code in a nested function's body may access (but not rebind) local variables of an outer function, also known as free variables of the nested function.

So, you would need to pass ctr to keyfunc explicitly.

danben
I've tried doing def keyfunc(x, k=ctr), and then doing k += 1. But that can't work, because then k becomes part of the local scope of the function and doesn't update the outer variable, ctr.
Thomas O
The updating of `ctr` needs to be done in its own scope. You might update it before or after calls to `keyfunc`.
danben
That wouldn't be possible since it's an argument to sorted() and is called by Python's internals, not by me directly. I couldn't put it in a lambda either because lambdas only allow expressions afaik.
Thomas O
@Thomas O: You can use `functools.partial` for this `key=functools.partial(keyfunc, ctr)`
ChristopheD
A: 

How about declaring ctr outside the class that write_postcodes belongs to, or any other class/function? This will make the variable accessible and writable.

Luiz C.
Yes, but it's a bit messy having global variables just for one or two functions - better to keep them in local scopes so you know why you put them there in the first place.
Thomas O
+2  A: 

Since the nested function can't rebind a nonlocal name (in Python 2; in Python 3, you'd use the nonlocal statement to enable that), you need to perform your incrementing without barename rebinding (by keeping the counter as an item or attribute of some barename, not as a barename itself). For example:

...
ctr = [0]
def keyfunc(x):
    ctr[0] += 1
    status("POSTCODE", "Sorting postcodes", ctr, len(self.postcodes))
    return x
...

and of course use ctr[0] wherever you're using bare ctr now elsewhere.

Alex Martelli
This seems almost too like a 'hack'. I'll use it, but it seems like a limitation of Python 2.x. Guess I'll be using 3.x soon though.
Thomas O