16

以下闭包函数在 javascript 中运行良好。

function generateNextNumber(startNumber) {
    var current = startNumber;
    return function(){
        return current += 1;
    }
}

var getNextNumber = generateNextNumber(10);
for (var i = 0; i < 10; i++) {
    console.log(getNextNumber());
}

我尝试在 Python 中做同样的事情

def generateNextNumber(startNumber):
    current = startNumber
    def tempFunction():
        current += 1
        return current
    return tempFunction

getNextNumber = generateNextNumber(10)
for i in range(10):
    print (getNextNumber())

我收到以下错误

Traceback (most recent call last):
  File "/home/thefourtheye/Desktop/Test1.py", line 10, in <module>
    print (getNextNumber())
  File "/home/thefourtheye/Desktop/Test1.py", line 4, in tempFunction
    current += 1
UnboundLocalError: local variable 'current' referenced before assignment

当我打印vars()and locals()insidetempFunction时,他们确认current存在。

({'current': 10}, {'current': 10})

但是当我像这样修改程序时

def generateNextNumber(startNumber):
    current = {"Number" : startNumber}
    def tempFunction():
        current["Number"] += 1
        return current["Number"]
    return tempFunction

有用。我无法解释为什么会这样。谁能解释一下?

4

2 回答 2

23

Python 假定函数中的所有变量都是局部变量。这是为了避免意外使用同名或封闭范围内的全局变量。在某些重要方面,这种差异是由于在 Python 中局部变量声明是自动/隐式的,而在 JavaScript 中则不是(你必须使用var)。解决方案:

使用global声明

def generateNextNumber(startNumber):
    global current
    current= startNumber
    def tempFunction():
        global current
        current += 1
        return current 
    return tempFunction

在某些情况下有效,但在您的情况下,只有一个实例tempFunction可以同时处于活动状态。

使用函数属性

def generateNextNumber(startNumber):
    def tempFunction():
        tempFunction.current += 1
        return tempFunction.current
    tempFunction.current= startNumber
    return tempFunction

使用函数是对象(因此可以具有属性)的事实,它们在声明时被实例化,并且它们成为封闭函数(或模块,在这种情况下它们实际上是全局的)的局部。这也有效,因为该名称tempFunction第一次在其自己的定义中使用“成员访问”.运算符,因此不假定为本地名称。“调用”()和“元素访问”[]运算符也会发生类似的情况。后一种情况解释了为什么您的代码有效。

强制将名称假定为非本地名称

def generateNextNumber(startNumber):
    current= type("OnTheFly",(),{})()
    current.value= startNumber
    def tempFunction():
        current.value += 1
        return current.value
    return tempFunction

这已经在上一节中解释过了。通过使用成员访问运算符.,我们说“current已经存在”,因此它是在封闭范围内搜索的。在这种特殊情况下,我们正在使用该type函数创建一个类并立即创建它的一个实例(使用第二组括号)。除了一般对象,我们还可以使用列表或字典。第二种情况是一个非常常见的解决方案。

使用函数对象

def generateNextNumber(startNumber):
    class TempFunction:
        def __call__(self):
            self.current += 1
            return self.current
    tempFunction= TempFunction()
    tempFunction.current= startNumber
    return tempFunction

其类具有调用方法的任何对象都是函数,因此可以使用函数调用运算符进行调用()。这与前两个案例极为相关。

使用nonlocal声明

def generateNextNumber(startNumber):
    current= startNumber
    def tempFunction():
        nonlocal current
        current += 1
        return current
    return tempFunction

同样,这global意味着......好吧,全局,nonlocal意味着“在前一个范围内”。在 Python 3 和更高版本的 Python 2 中有效。

使用生成器

def generateNextNumber(current):
    while True :
        current+= 1
        yield current

这可能是解决非局部变量访问的一般问题的最“Pythonic”方法,而是您用来解释它的特定案例。我不能不提它。但是,您需要稍作改动来调用它:

getNextNumber = generateNextNumber(10)
for i in range(10):
    print (getNextNumber.next())

当驱动for调用时next()是隐式的(但生成器不能像我的示例中那样是无限的)。

于 2013-08-29T06:05:17.937 回答
3

Python 通过定义函数包含赋值的任何变量都是局部变量来决定函数的局部变量是什么,除非声明了nonlocalor global。因此,

current += 1

创建一个名为current隐藏非局部变量的局部变量。如果您使用的是 Python 2,则标准解决方案(除了尽量不这样做)是制作current一个 1 元素列表并使用

current[0] += 1

作为参考,“尽量不这样做”可能类似于以下内容:

class Counter(object):
    def __init__(self):
        self.count = 0
    def __call__(self):
        self.count += 1
        return self.count
c = Counter()
c()  # Returns 1
c()  # Returns 2
于 2013-08-29T04:10:23.583 回答