在Python中返回累加器的函数

问题描述 投票:5回答:4

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

问题是:

我们想要编写一个生成累加器的函数 - 一个取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的新手。为什么第一个解决方案不起作用而第二个解决方案不起作用?作者提到了词汇变量,但我仍然没有得到它。

python scope closures common-lisp local-variables
4个回答
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
投票

无限生成器是一种实现。您可以在生成器实例上调用__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

0
投票

以下将有效:

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的第一个元素。


-1
投票

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

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

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

© www.soinside.com 2019 - 2024. All rights reserved.