Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Read/Write Python Closures

Closures are an incredibly useful language feature. They let us do clever things that would otherwise take a lot of code, and often enable us to write code that is more elegant and more clear. In Python 2.x, closures variable names cannot be rebound; that is, a function defined inside another lexical scope cannot do something like some_var = 'changed!' for variables outside of its local scope. Can someone explain why that is? There have been situations in which I would like to create a closure that rebinds variables in the outer scope, but it wasn't possible. I realize that in almost all cases (if not all of them), this behavior can be achieved with classes, but it is often not as clean or as elegant. Why can't I do it with a closure?

Here is an example of a rebinding closure:

def counter():     count = 0     def c():         count += 1         return count     return c 

This is the current behavior when you call it:

>>> c() Traceback (most recent call last):   File "<stdin>", line 1, in <module>   File "<stdin>", line 4, in c UnboundLocalError: local variable 'count' referenced before assignment 

What I'd like it to do instead is this:

>>> c() 1 >>> c() 2 >>> c() 3 
like image 871
Benson Avatar asked Jan 05 '10 21:01

Benson


People also ask

How do you write a closure function in Python?

The criteria that must be met to create closure in Python are summarized in the following points. We must have a nested function (function inside a function). The nested function must refer to a value defined in the enclosing function. The enclosing function must return the nested function.

What is __ closure __ in Python?

A closure is a function object that remembers values in enclosing scopes even if they are not present in memory. The __closure__ attribute of a closure function returns a tuple of cell objects. This cell object also has an attribute called cell_contents, which returns returns the contents of the cell.

Why we use closures in Python?

Closure in Python can be defined when a nested function references a value in its enclosing scope. Closures provide some form of data hiding. A closure can also be a highly efficient way to preserve state across a series of function calls.


2 Answers

To expand on Ignacio's answer:

def counter():     count = 0     def c():         nonlocal count         count += 1         return count     return c  x = counter() print([x(),x(),x()]) 

gives [1,2,3] in Python 3; invocations of counter() give independent counters. Other solutions - especially using itertools/yield are more idiomatic.

like image 178
sdcvvc Avatar answered Sep 26 '22 06:09

sdcvvc


You could do this and it would work more or less the same way:

class counter(object):     def __init__(self, count=0):         self.count = count     def __call__(self):         self.count += 1         return self.count     

Or, a bit of a hack:

def counter():     count = [0]     def incr(n):         n[0] += 1         return n[0]     return lambda: incr(count) 

I'd go with the first solution.

EDIT: That's what I get for not reading the big blog of text.

Anyway, the reason Python closures are rather limited is "because Guido felt like it." Python was designed in the early 90s, in the heyday of OO. Closures were rather low on the list of language features people wanted. As functional ideas like first class functions, closures, and other things make their way into mainstream popularity, languages like Python have had to tack them on, so their use may a bit awkward, because that's not what the language was designed for.

<rant on="Python scoping">

Also, Python (2.x) has rather odd (in my opinion) ideas about scoping that interferes with a sane implementation of closures, among other things. It always bothers me that this:

new = [x for x in old] 

Leaves us with the name x defined in the scope we used it in, as it is (in my opinion) a conceptually smaller scope. (Though Python gets points for consistency, as doing the same thing with a for loop has the same behavior. The only way to avoid this is to use map.)

Anyway, </rant>

like image 21
Chris Lutz Avatar answered Sep 25 '22 06:09

Chris Lutz