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.