nonlocal keyword in Python 2.x

2019-01-02 23:01发布

I'm trying to implement a closure in Python 2.6 and I need to access a nonlocal variable but it seems like this keyword is not available in python 2.x. How should one access nonlocal variables in closures in these versions of python?

10条回答
趁早两清
2楼-- · 2019-01-02 23:03

There is a wart in python's scoping rules - assignment makes a variable local to its immediately enclosing function scope. For a global variable, you would solve this with the global keyword.

The solution is to introduce an object which is shared between the two scopes, which contains mutable variables, but is itself referenced through a variable which is not assigned.

def outer(v):
    def inner(container = [v]):
        container[0] += 1
        return container[0]
    return inner

An alternative is some scopes hackery:

def outer(v):
    def inner(varname = 'v', scope = locals()):
        scope[varname] += 1
        return scope[varname]
    return inner

You might be able to figure out some trickery to get the name of the parameter to outer, and then pass it as varname, but without relying on the name outer you would like need to use a Y combinator.

查看更多
该账号已被封号
3楼-- · 2019-01-02 23:03

Extending Martineau elegant solution above to a practical and somewhat less elegant use case I get:

class nonlocals(object):
""" Helper to implement nonlocal names in Python 2.x.
Usage example:
def outer():
     nl = nonlocals( n=0, m=1 )
     def inner():
         nl.n += 1
     inner() # will increment nl.n

or...
    sums = nonlocals( { k:v for k,v in locals().iteritems() if k.startswith('tot_') } )
"""
def __init__(self, **kwargs):
    self.__dict__.update(kwargs)

def __init__(self, a_dict):
    self.__dict__.update(a_dict)
查看更多
forever°为你锁心
4楼-- · 2019-01-02 23:04

Here's something inspired by a suggestion Alois Mahdal made in a comment regarding another answer:

class Nonlocals(object):
    """ Helper class to implement nonlocal names in Python 2.x """
    def __init__(self, **kwargs):
        self.__dict__.update(kwargs)

def outer():
    nonlocals = Nonlocals(y=0)
    def inner():
        nonlocals.y += 1
        return nonlocals.y
    return inner

f = outer()
print(f(), f(), f()) # -> (1 2 3)

Update

After looking back at this recently, I was struck by how decorator-like it was—and then it dawned on my that it could formally be turned into one which would make it more generic & useful (although doing so degrades its readability somewhat).

# Implemented as a decorator.

class Nonlocals(object):
    """ Decorator class to help implement nonlocal names in Python 2.x """
    def __init__(self, **kwargs):
        self._vars = kwargs

    def __call__(self, func):
        for k, v in self._vars.items():
            setattr(func, k, v)
        return func

@Nonlocals(y=0)
def outer():
    def inner():
        outer.y += 1
        return outer.y
    return inner


f = outer()
print(f(), f(), f()) # -> (1 2 3)

Also note that the code in both versions will still work in Python 3.x.

查看更多
该账号已被封号
5楼-- · 2019-01-02 23:09

Another way to do it (although it's too verbose):

import ctypes

def outer():
    y = 0
    def inner():
        ctypes.pythonapi.PyCell_Set(id(inner.func_closure[0]), id(y + 1))
        return y
    return inner

x = outer()
x()
>> 1
x()
>> 2
y = outer()
y()
>> 1
x()
>> 3
查看更多
倾城 Initia
6楼-- · 2019-01-02 23:13

I think the key here is what you mean by "access". There should be no issue with reading a variable outside of the closure scope, e.g.,

x = 3
def outer():
    def inner():
        print x
    inner()
outer()

should work as expected (printing 3). However, overriding the value of x does not work, e.g.,

x = 3
def outer():
    def inner():
        x = 5
    inner()
outer()
print x

will still print 3. From my understanding of PEP-3104 this is what the nonlocal keyword is meant to cover. As mentioned in the PEP, you can use a class to accomplish the same thing (kind of messy):

class Namespace(object): pass
ns = Namespace()
ns.x = 3
def outer():
    def inner():
        ns.x = 5
    inner()
outer()
print ns.x
查看更多
走好不送
7楼-- · 2019-01-02 23:16

Inner functions can read nonlocal variables in 2.x, just not rebind them. This is annoying, but you can work around it. Just create a dictionary, and store your data as elements therein. Inner functions are not prohibited from mutating the objects that nonlocal variables refer to.

To use the example from Wikipedia:

def outer():
    d = {'y' : 0}
    def inner():
        d['y'] += 1
        return d['y']
    return inner

f = outer()
print(f(), f(), f()) #prints 1 2 3
查看更多
登录 后发表回答