closure = decorator?

Terry Reedy tjreedy at udel.edu
Fri Oct 11 16:51:15 EDT 2013


On 10/11/2013 12:44 PM, Steven D'Aprano wrote:
> On Fri, 11 Oct 2013 15:01:40 +0300, Jussi Piitulainen wrote:
>
>> Steven D'Aprano writes:
>>> Closures have nothing to do with *arguments*. A better definition of a
>>> closure is that it is a function together with a snapshot of the
>>> environment it was called from.
> [...]
>> Second, it's precisely not (a snapshot of) the environment where the
>> function is *called* from, it's (a snapshot of) the environment where
>> the function was *created* in. This is the whole *point*.
>
> Ah yes, of course you are right. I actually knew that, it was a slip of
> the brain that I wrote it wrong :-(
>
> Thanks for the correction.

The closure is also not a 'snapshot' but a reference to (or preservation 
of) (relevant parts of) the environment. A snapshot of the environment 
at the time of definition would have been much easier to implement.

x = 1
def outer():
     y = 1
     def inner():
         return x + y
     y = 2
     return inner
x = 2
print(outer()())
# 4

In a sense, all user functions are closures in that they have, and have 
always had, a reference to their definition module environment -- the 
readonly .__globals__ attribute (probably .func_globals in 2.x).

This lexical, as opposed to dynamic scoping, becomes noticable when one 
import a function from another module, as for testing. Because 
.__globals__ is read-only, one must monkey-patch the module of 
definition to change the function's global (modular) environment, as 
when replacing an object it uses with a mock. I ran into this when 
testing Idle methods that use a tk message box to display a message and 
wait for input (sometimes text, always a mouse click) from a human user.

What is relatively new (and tricky) is capturing local names of 
surrounding functions while maintaining both late binding and 
independent writability for each closure. This last means that the 
following works:

def account():
     balance = 0
     def trans(amt):
         nonlocal balance
         balance += amt
         return balance
     return trans

xmasfund = account()
pettycash = account()
print(xmasfund(100))
# 100
print(pettycash(50))
# 50
print(xmasfund(-100))
# 0
print(pettycash(-25))
# 25

Closures and decorators are *really* two different subjects.

-- 
Terry Jan Reedy




More information about the Python-list mailing list