Skip to content

Instantly share code, notes, and snippets.

Created August 22, 2013 04:07
Show Gist options
  • Save anonymous/6303131 to your computer and use it in GitHub Desktop.
Save anonymous/6303131 to your computer and use it in GitHub Desktop.
class A(object):
def hook(self, f):
atime = 0
def intime(*args):
print atime
atime += 1
return f(*args)
return intime
>>> f = A().hook(lambda b: b + 1)
>>> f(1)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 5, in intime
UnboundLocalError: local variable 'atime' referenced before assignment
>>>
@sjl
Copy link

sjl commented Aug 22, 2013

OK, so this is Python being shitty. Here's the deal.

First, atime += 1 is essentially the same as atime = atime + 1. Nothing crazy there, but it's important.

When Python is creating the intime closure, it tries to determine what variables to capture. Anything that's "non-local" to the function will be captured, but variables that are set inside the function aren't.

So when Python sees that you're assigning atime a value inside the function, it assumes that you want to declare a local atime variable in the function. So it doesn't use the one outside.

And then when you actually call this function, you try to use atime in the print before it's defined, and it explodes.

This is all only a problem because Python doesn't have separate syntax for declaring and assigning variables. Like, in Scala you have:

var foo = 10
foo = 20

Scala knows that the second line is trying to set an existing variable and not declare a new one. Python doesn't have that luxury.

Here's the long version if you want to read more: http://www.python.org/dev/peps/pep-3104/

@mwhooker
Copy link

Thanks @sjl. it makes sense now. surprised I've never run in to this before.

@mattrobenolt came up with an alternate soltution: https://gist.github.com/mattrobenolt/6303154

here's the correct way to do it

class A(object):
    def hook(self, f):
        atime = []

        def intime(*args):
            print sum(atime)
            atime.append(1)
            return f(*args)
        return intime

f = A().hook(lambda b: b + 1)


for i in range(10):
    f(i)

and the python 3 way. bout time we start using it I think.

#!/usr/bin/env python3

class A(object):
    def hook(self, f):
        atime = 0
        def intime(*args):
            nonlocal atime
            print(atime)
            atime += 1
            return f(*args)
        return intime

f = A().hook(lambda b: b + 1)

# works as expected
for i in range(10):
    f(i)

and javascript for the fuck of it. and because someone once told me js and python had the same scope behavior.

a = function(f) {
    var a = 0;
    return function(i) {
        console.log(a);
        a = a+1;
        return f(i);
    }
}(function(x) { return x + 1 });

for (i = 0; i < 10; i += 1) {
  a(i);
}

@jdunck
Copy link

jdunck commented Aug 22, 2013

Incidentally, when people complain about python not having proper closure support, this is (sometimes) what they mean.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment