为什么这个闭包没有修改封闭范围内的变量?

jwd*_*jwd 19 python closures generator

这一点Python不起作用:

def make_incrementer(start):
    def closure():
        # I know I could write 'x = start' and use x - that's not my point though (:
        while True:
            yield start
            start += 1
    return closure

x = make_incrementer(100)
iter = x()
print iter.next()    # Exception: UnboundLocalError: local variable 'start' referenced before assignment
Run Code Online (Sandbox Code Playgroud)

我知道如何解决这个错误,但请耐心等待:

这段代码工作正常:

def test(start):
    def closure():
        return start
    return closure

x = test(999)
print x()    # prints 999
Run Code Online (Sandbox Code Playgroud)

为什么我可以读取start闭包内的变量而不是写入它?导致这种start变量处理的语言规则是什么?

更新:我发现这个SO帖子相关(答案不仅仅是问题):读/写Python闭包

And*_*ark 32

无论何时在函数内部分配变量,它都将是该函数的局部变量.该行start += 1正在分配一个新值start,因此start是一个局部变量.由于start存在局部变量start,因此当您第一次尝试访问它时,该函数将不会尝试查看全局范围,因此您会看到错误.

在3.x中,如果使用nonlocal关键字,您的代码示例将起作用:

def make_incrementer(start):
    def closure():
        nonlocal start
        while True:
            yield start
            start += 1
    return closure
Run Code Online (Sandbox Code Playgroud)

在2.x上,您通常可以通过使用global关键字解决类似问题,但这不起作用,因为start它不是全局变量.

在这种情况下,您可以执行类似于建议(x = start)的操作,或者使用可修改的变量来修改并生成内部值.

def make_incrementer(start):
    start = [start]
    def closure():
        while True:
            yield start[0]
            start[0] += 1
    return closure
Run Code Online (Sandbox Code Playgroud)


agf*_*agf 10

在Python 2.x上有两个"更好"/更多Pythonic方法,而不是使用容器来解决缺少非本地关键字的问题.

您在代码中的注释中提到的一个 - 绑定到局部变量.还有另一种方法:

使用默认参数

def make_incrementer(start):
    def closure(start = start):
        while True:
            yield start
            start += 1
    return closure

x = make_incrementer(100)
iter = x()
print iter.next()
Run Code Online (Sandbox Code Playgroud)

这具有局部变量的所有好处,而无需额外的代码行.它也发生在x = make_incrememter(100)线路而不是iter = x()线路上,根据情况可能或可能不重要.

您还可以使用"不实际分配给引用变量"方法,以比使用容器更优雅的方式:

使用函数属性

def make_incrementer(start):
    def closure():
        # You can still do x = closure.start if you want to rebind to local scope
        while True:
            yield closure.start
            closure.start += 1
    closure.start = start
    return closure

x = make_incrementer(100)
iter = x()
print iter.next()    
Run Code Online (Sandbox Code Playgroud)

这适用于所有最新版本的Python,并利用这样的事实:在这种情况下,您已经拥有一个对象,您知道您可以引用属性的名称 - 不需要为此目的创建新容器.