Preface
In this article I will introduce you to closures in Python 3. Actually, a closure is a function in Python which allows you to do things that aren’t possible in another language (like C/C++ or Java) with functions. In Python everything is treated like a first-class citizen, so they can be passed around just as normal variables. So are functions too. You probably have seen code like this already:
- >>> def adder(a, b):
- ... return a + b
- ...
- >>> def caller(fun):
- ... print(fun(2, 4))
- ... print(fun(3, 5))
- ...
- >>> caller(adder)
- 6
- 8
Because we can pass around functions as arguments we can return functions too from function calls. Let's consider the following example:
- >>> def contains_factory(x):
- ... def contains(lst):
- ... return x in lst
- ... return contains
- ...
- >>> contains_15 = contains_factory(15)
And these functions are closures.
What makes closures special?
If you think that closures are ordinary functions you might have missed the key difference from the previous example.
Call the contains_15 with some lists / iterables to test the functionality:
- >>> contains_15([1,2,3,4,5])
- False
- >>> contains_15([13, 14, 15, 16, 17])
- True
- >>> contains_15(range(1, 20))
- True
Another interesting fact is, that the closure remains existing even if the original creator function (in the example case it is contains_factory) is deleted:
- >>> del contains_factory
- >>> contains_factory(42)
- Traceback (most recent call last):
- File "
" , line 1, in - NameError: name 'contains_factory' is not defined
- >>> contains_15(range(14, 20))
- True
After this introduction on closures you should know how to create closures in Python. But for the sake of brevity let's sum things up:
Pretty simple, isn't it? The second point is simple in reality, optional -- but if you do not reference a variable from the enclosing function there is not much sense to create a nested function and return it -- you simply define a function. You could do this in the normal scope too.
Let's create another interesting closure: a counter. The idea behind the counter is that in some cases you just want to count interactions. In those cases you define a global variable (most of the time called counter) and increment it at the right place when an interaction occurs. We can replace this global variable and the incrementation by defining a closure and call this closure every time we want to count something:
- >>> def counter_factory():
- ... count = 0 # here I create the variable to increment
- ... def counter():
- ... nonlocal count
- ... count += 1
- ... return count
- ... return counter
- ...
- >>> counter1 = counter_factory()
- >>> counter1()
- 1
- >>> counter1()
- 2
- >>> counter2 = counter_factory()
- >>> counter2()
- 1
- >>> counter1()
- 3
- >>> counter2()
- 2
- counter1() == 1
- test.py
- def counter_factory():
- count = [0] # This variable is created to do increment
- def counter():
- count[0] += 1
- return count[0]
- def current():
- return count[0]
- return (counter, current)
As you can see, the incrementer increments silently the value of count inside the closure. getter returns the actual value of count without incrementing it -- and this makes the feature usable. Resetting the counter is done by simply re-assigning the closures.
What are closures good for?
Even if closures seem pretty interesting (a function returning another function which knows its creation context!) there is another question: where can we utilize closures to make the best of them?
Here are a few uses for closures:
Late binding of closures
A source of problems is the way Python binds the variables in closures. For example you write:
- def create_adders():
- adders = []
- for i in range(10):
- def adder(x):
- return i + x
- adders.append(adder)
- return adders
- for adder in create_adders():
- print(adder(1))
In reality you get:
What happens? You create 10 functions which add 1 to the number 9. This is because of the late-binding of closures: the variables used inside the closures are looked up at the time the inner function is called. Whenever the adder function is called the inner loop over the range(10) is finished and the variable i has the value 9 -- so you end up with 10 for each 10 functions as result.
To solve this problem you could add an additional parameter to your closures which will maintain the right state:
- def create_adders():
- adders = []
- for i in range(10):
- def adder(x, i=i): # Use parameter i to hold the value during iteration
- return i + x
- adders.append(adder)
- return adders
Conclusion
We have seen that closures are functions which remember the context (lexical scope) in which they were created and they can use it even if the program flow is no longer in the enclosing scope. To create a closure you need to write a function which returns another function. The returned function is the closure itself.
Closure variables are late binding, so take care when you use them: you may end up with unwanted results!
Supplement
* Python Closures and the Python 2.7 nonlocal Solution
沒有留言:
張貼留言