我正在阅读黑客和画家,并对作者提到的问题感到困惑,以说明不同编程语言的力量。
问题是:
我们想要编写一个生成累加器的函数 - 一个取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的新手。为什么第一个解决方案不起作用而第二个解决方案不起作用?作者提到了词汇变量,但我仍然没有得到它。
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,但重要的问题是计算和分配是在两个单独的步骤中执行的。
无限生成器是一种实现。您可以在生成器实例上调用__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
以下将有效:
def foo(n):
s = n
def bar(i):
s_ = s + i
return s_
return bar
内部函数bar
看起来在其范围内局部找到s
,如果它找不到它,它会在封闭范围内看起来一层,它找到s
这是foo
的局部变量。但是如果你说s = s + 1
,你将s
声明为bar
范围内的新局部变量(“赋值语句在本地范围内创建变量”),这会导致错误,因为你之前没有为s
赋值添加一些东西(引用它)。
在另一个例子中,说s[0] = s[0] + 1
是不同的,因为你没有在条形内声明一个新的局部变量,你可以访问在条形的封闭范围内找到的s
的第一个元素。
在Python中,如果我们使用一个变量并将其传递给一个函数,那么它将是Call by Value,无论你对变量做什么改变都不会反映到原始变量。
但是当您使用列表而不是变量时,您对函数中的列表所做的更改将反映在函数外部的原始列表中,因此这称为按引用调用。
这就是第二个选项确实有效的原因,第一个选项没有。