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

时间:2011-09-23 23:30:35

标签: 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

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

此代码可以正常工作:

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

x = test(999)
print x()    # prints 999

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

更新:我发现此帖子相关(答案不仅仅是问题):Read/Write Python Closures

4 个答案:

答案 0 :(得分:30)

每当在函数内部分配变量时,它将是该函数的局部变量。行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

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

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

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

答案 1 :(得分:9)

在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()

这具有局部变量的所有好处,而无需额外的代码行。它也发生在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()    

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

答案 2 :(得分:4)

Example

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[0]
            start[0] += 1
    return closure

x = make_incrementer([100])
iter = x()
print iter.next()

答案 3 :(得分:3)

在Python 3.x中,您可以使用nonlocal关键字来重新绑定不在本地范围内的名称。在2.x中,您唯一的选择是修改(或变异)闭包变量,将实例变量添加到内部函数,或者(因为您不想这样做)创建一个局部变量......

# modifying  --> call like x = make_incrementer([100])
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[0]
            start[0] += 1
    return closure

# adding instance variables  --> call like x = make_incrementer(100)
def make_incrementer(start):
    def closure():
        while True:
            yield closure.start
            closure.start += 1
    closure.start = start
    return closure

# creating local variable  --> call like x = make_incrementer(100)
def make_incrementer(start):
    def closure(start=start):
        while True:
            yield start
            start += 1
    return closure