Python 2.x中的非本地关键字

时间:2010-07-06 22:31:11

标签: python closures python-2.x python-nonlocal

我正在尝试在Python 2.6中实现一个闭包,我需要访问一个非局部变量,但似乎这个关键字在python 2.x中不可用。如何在这些python版本中访问闭包中的非局部变量?

10 个答案:

答案 0 :(得分:114)

内部函数可以在2.x中读取非局部变量,而不是重新绑定它们。这很烦人,但你可以解决它。只需创建一个字典,并将数据存储在其中。 变异非局部变量引用的对象不禁止内部函数。

使用维基百科的例子:

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

答案 1 :(得分:37)

以下解决方案受到answer by Elias Zamaria的启发,但与该答案相反,它正确处理外部函数的多次调用。 “变量”inner.y是当前outer调用的本地。只有它不是变量,因为它是禁止的,而是一个对象属性(对象是函数inner本身)。这非常难看(请注意,该属性只能在定义inner函数后创建),但似乎有效。

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

f = outer()
g = outer()
print(f(), f(), g(), f(), g()) #prints (1, 2, 1, 3, 2)

答案 2 :(得分:28)

非本地课程相比,不是字典。修改@ ChrisB' example

def outer():
    class context:
        y = 0
    def inner():
        context.y += 1
        return context.y
    return inner

然后

f = outer()
assert f() == 1
assert f() == 2
assert f() == 3
assert f() == 4

每个outer()调用都会创建一个名为context的新的不同类(不仅仅是一个新实例)。因此,它避免了@Nathaniel's beware关于共享上下文。

g = outer()
assert g() == 1
assert g() == 2

assert f() == 5

答案 3 :(得分:14)

我认为这里的关键是“访问”的意思。读取闭包范围之外的变量应该没有问题,例如,

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

应按预期工作(打印3)。但是,覆盖x的值不起作用,例如,

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

仍会打印3.根据我对PEP-3104的理解,这是非本地关键字的含义。正如PEP中提到的,你可以使用一个类来完成同样的事情(有点混乱):

class Namespace(object): pass
ns = Namespace()
ns.x = 3
def outer():
    def inner():
        ns.x = 5
    inner()
outer()
print ns.x

答案 4 :(得分:12)

在Python 2中有另一种实现非本地变量的方法,如果这里的任何答案因任何原因而不受欢迎:

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

f = outer()
print(f(), f(), f()) #prints 1 2 3

在变量的赋值语句中使用函数的名称是多余的,但它看起来比将变量放在字典中更简单,更清晰。从一个电话到另一个电话会记住这个值,就像Chris B.的回答一样。

答案 5 :(得分:10)

以下是Alois Mahdal在comment中就另一个answer提出的建议的启发:

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


def outer():
    nl = Nonlocal(y=0)
    def inner():
        nl.y += 1
        return nl.y
    return inner

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

<强>更新

最近回顾过这个问题之后,我很惊讶它是如何装饰的 - 当我突然意识到将它作为一个实现它会使它更通用&amp;有用(尽管这样做可以说会在一定程度上降低其可读性)。

# Implemented as a decorator.

class Nonlocal(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


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


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

请注意,这两个版本都适用于Python 2和3。

答案 6 :(得分:3)

python的范围规则中存在一个瑕疵 - 赋值使其变量本地化为其直接封闭的函数范围。对于全局变量,您可以使用global关键字解决此问题。

解决方案是引入一个在两个作用域之间共享的对象,它包含可变变量,但它本身是通过未分配的变量引用的。

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

另一种选择是一些范围hackery:

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

您可以找出一些技巧来获取参数的名称outer,然后将其作为varname传递,但不依赖于您希望使用的名称outer Y组合器。

答案 7 :(得分:3)

另一种方法(虽然它太冗长):

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

答案 8 :(得分:0)

将Martineau优雅的解决方案扩展到我得到的实用且稍微不那么优雅的用例:

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)

答案 9 :(得分:-3)

使用全局变量

def outer():
    global y # import1
    y = 0
    def inner():
        global y # import2 - requires import1
        y += 1
        return y
    return inner

f = outer()
print(f(), f(), f()) #prints 1 2 3

就个人而言,我不喜欢全局变量。但是,我的提案基于https://stackoverflow.com/a/19877437/1083704回答

def report():
        class Rank: 
            def __init__(self):
                report.ranks += 1
        rank = Rank()
report.ranks = 0
report()

每当您需要调用ranks时,用户需要声明全局变量report。我的改进消除了从用户初始化函数变量的需要。