Simulating nonlocal in Python 2.x
Closure is truly wonderful. JavaScript — despite its plethora of quirks — is now widely appreciated, thanks in large part to its lexical scoping. Python 3 is lexically-scoped, too, as the following code demonstrates.
def cache(saved=None): def _(thing=None): nonlocal saved if thing is not None: saved = thing return saved return _ cache = cache()
If (the rebound) cache is passed no arguments (or None), saved is
returned. Otherwise, thing is assigned to saved and returned.
>>> cache(2**3) 8 >>> cache() 8
This works thanks to the nonlocal keyword introduced in Python 3, which
enables variables in outer scopes to be rebound. So how would one achieve
the same result in earlier versions of Python?
Bringing lexical scoping to Python 2.x
def cache(saved=None): def _(thing=None): # nonlocal saved if thing is not None: saved = thing return saved return _ cache = cache()
The nonlocal line is commented out as it's a syntax error in Python 2.x.
>>> cache(2**3) 8 >>> cache() ... UnboundLocalError: local variable 'saved' referenced before assignment
When cache is passed a (non-None) thing, a new saved is created
within the local scope. When cache is passed no arguments (or None),
execution skips to return saved. At this point, saved is expected to exist
within the local scope – it does not, which explains the UnboundLocalError.
It is possible to simulate lexical scoping in Python 2.x. The approaches I find most palatable utilize a dictionary or a function object as a namespace accessible to both the inner and outer functions.
Dictionary
def cache(): ns = {'saved': None} def _(thing=None): if thing is not None: ns['saved'] = thing return ns['saved'] return _ cache = cache()
Function object
def cache(): def ns(): pass ns.saved = None def _(thing=None): if thing is not None: ns.saved = thing return ns.saved return _ cache = cache()
The dictionary approach is arguably more correct, but subscript notation hurts
my eyes so I prefer to stick things on a function object. It's useful that
def ns(): pass looks odd, as it alerts the reader to the fact that something
is odd.