在Python

时间:2018-03-01 09:31:08

标签: python scope closures common-lisp local-variables

我正在阅读黑客和画家,并对作者提到的问题感到困惑,以说明不同编程语言的强大功能。

问题是:

  

我们想要编写一个生成累加器的函数 - 一个带有数字 n 的函数,并返回一个带有另一个数字 i 并返回 n的函数增加 i 。 (那是增加,而不是加号。累加器必须累积。)

作者提到了几种使用不同编程语言的解决方案。例如,Common Lisp:

(defun foo (n)
  (lambda (i) (incf n i)))

和JavaScript:

function foo(n) { return function (i) { return n += i } }

但是,当谈到Python时,以下代码不起作用:

def foo(n):
    s = n
    def bar(i):
        s += i
        return s
    return bar

f = foo(0)
f(1)  # UnboundLocalError: local variable 's' referenced before assignment

一个简单的修改就可以了:

def foo(n):
    s = [n]
    def bar(i):
        s[0] += i
        return s[0]
    return bar

我是Python新手。为什么第一个解决方案不起作用而第二个解决方案不起作用?作者提到了词汇变量,但我仍然没有得到它。

4 个答案:

答案 0 :(得分:5)

s += i只是s = s + i的糖。 *

这意味着您为变量s分配了一个新值(而不是将其变异)。分配给变量时,Python假定它是函数的本地变量。但是,在分配之前,需要评估s + i,但s是本地的,仍然未分配 - >错误。

在第二种情况s[0] += i中,您永远不会直接分配给s,而只能访问s中的某个项目。因此,Python可以清楚地看到它不是局部变量,而是在外部范围内寻找它。

最后,一个更好的选择(在Python 3中)是明确地告诉它s不是局部变量:

def foo(n):
    s = n
    def bar(i):
        nonlocal s
        s += i
        return s
    return bar

(实际上不需要s - 您只需在n内使用bar即可。)

* The situation is slightly more complex,但重要的问题是计算和分配是在两个单独的步骤中执行的。

答案 1 :(得分:1)

无限生成器是一种实现。您可以在生成器实例上调用__next__以迭代地提取连续结果。

def incrementer(n, i):
    while True:
        n += i
        yield n

g = incrementer(2, 5)

print(g.__next__())  # 7
print(g.__next__())  # 12
print(g.__next__())  # 17

如果您需要灵活的增量器,一种可能性是面向对象的方法:

class Inc(object):
    def __init__(self, n=0):
        self.n = n
    def incrementer(self, i):
        self.n += i
        return self.n

g = Inc(2)

g.incrementer(5)  # 7
g.incrementer(3)  # 10
g.incrementer(7)  # 17

答案 2 :(得分:0)

以下内容可行:

def foo(n):
    s = n
    def bar(i):
        s_ = s + i
        return s_
    return bar

内部函数bar看起来在其范围内本地查找s,如果找不到它,它会在找到{{1}的封闭范围内查找一级}这是s的局部变量。但是,如果您说foo,则将s = s + 1声明为s范围内的新局部变量("赋值语句在本地范围内创建变量") ,这会导致错误,因为在向其添加内容之前,您尚未为bar分配值(引用它)。

在另一个例子中,说s是不同的,因为你没有在栏中声明一个新的局部变量,你可以访问s[0] = s[0] + 1的第一个元素,它可以在条形的封闭范围内找到。

答案 3 :(得分:-1)

在Python中,如果我们使用一个变量并将其传递给一个函数,那么它将是Call by Value,无论你对变量做出什么改变都不会反映到原始变量。

但是当您使用列表而不是变量时,您对函数列表所做的更改将反映在函数外部的原始列表中,因此这称为按引用调用。

这就是第二个选项确实有效的原因,第一个选项没有。