Python has closures, and Python does lexical scoping.
But.
In a quite retarded way, in the 2.x series. In Python 2.x, the closures aren't really closures, but some read-only thing.
Once you learn about closures, that makes click with all the niceties of
functional programming that Python has, and you kind of expect that all of them
work and interoperate properly. Python has lambda
, functions are first-class
citizens there, there is an apply
equivalent, map
... it makes sense. It
should support closures.
It does:
def gen_printer(x):
def printer():
print x
return printer
p1 = gen_printer(1)
p2 = gen_printer(2)
p1() # 1
p2() # 2
But it turns out that real closures are not supported. A trivial example (code that I tried to use in a real-world problem) showing a funny issue follows:
def create_counter(initial_point):
def cnt():
c = initial_point
initial_point = initial_point + 1
return c
return cnt
counter_from_1 = create_counter(1)
print counter_from_1()
------
UnboundLocalError: local variable 'initial_point' referenced before assignment
Assignment implicitly creates a local name (in the inner function namespace), even if the name does exist in an outer scope. Also, regardless of the location of the assignment (in this case, even after a read to it). That's the cause of the previous error.
What happens is this: when Python sees a request for a variable, it looks for it
in the current (local) namespace, then the enclosing namespace (say, the one of
the enclosing function), and so on, until the global namespace, and finally the
builtins' namespace. But only if you are reading the variable; if you try to
change its value, Python assumes that such variable must be in the local
namespace; that's the reason for UnboundLocalError
. And there is really no
proper fix for this in Python 2.x; there are ugly hacks storing the required
state in object attributes, which don't really count as "supporting closures".
Fortunately, Python 3 does support closures, through the nonlocal
keyword. The
following Python 3 code does what you would expect:
def create_counter(initial_point):
def cnt():
nonlocal initial_point
c = initial_point
initial_point = initial_point + 1
return c
return cnt
counter_from_1 = create_counter(10)
print(counter_from_1())