【问题标题】:Function that returns an accumulator in Python在 Python 中返回累​​加器的函数
【发布时间】:2023-03-23 09:25:01
【问题描述】:

我正在阅读 Hackers and Painters 并且对作者提到的一个问题感到困惑,以说明不同编程语言的力量。

问题是:

我们想写一个生成累加器的函数——一个接受数字 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 新手。为什么第一个解决方案不起作用,而第二个解决方案起作用?作者提到了词法变量,但我还是不明白。

【问题讨论】:

  • A def 引入了一个新范围。如果没有 globalnonlocal,则无法在外部范围内重新绑定变量。

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


【解决方案1】:

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 - 您可以简单地在bar 中使用n。)

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

【讨论】:

  • s += i 只是 s = s + i 的糖——请注意,对于某些类型来说这不是真的。在列表上使用 += 绝对会改变它(通过__iadd__)。
  • 谢谢,现在我明白了。我使用了一个多余的s,只是为了让两种情况之间的比较更清楚。顺便说一句,感谢您纠正我糟糕的语法。 :)
  • @Norrius 是正确的,但它使解释更容易 :) 范围规则也适用于这些类型。
  • "你可以简单地使用n 代替bar" 写n = n + i? Python 不会为它创建一个新的本地 n 吗?
  • @WillNess 当然。函数参数就像变量一样,因此您必须在 bar 内将其声明为 nonlocal
【解决方案2】:

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

【讨论】:

  • 可以,但不灵活,因为增量固定为 5。
  • 谢谢,这是正确的,虽然比使用闭包的更冗长。 :)
  • 有时,冗长更像是pythonic :)。但这更多是风格/偏好的问题。我个人认为使用类而不是 global / nonlocal 语句更干净。
【解决方案3】:

在 Python 中,如果我们使用变量并将其传递给函数,那么它将是按值调用,无论您对变量所做的任何更改都不会反映到原始变量中。

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

这就是第二个选项有效而第一个选项无效的原因。

【讨论】:

    猜你喜欢
    • 2018-11-21
    • 2015-12-09
    • 1970-01-01
    • 2018-06-12
    • 2017-06-12
    • 2016-01-29
    • 1970-01-01
    • 1970-01-01
    • 2018-07-29
    相关资源
    最近更新 更多