【问题标题】:Python closure vs javascript closurePython 闭包 vs javascript 闭包
【发布时间】:2013-08-29 04:04:02
【问题描述】:

以下闭包函数在 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

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

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

但是当我把程序修改成这样的时候

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

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

【问题讨论】:

  • 这是 Python 中那些奇怪的小角落之一。我相信它可能已在 Python 3.x 中修复。见stackoverflow.com/a/3190783/646543
  • @Michael0x2a 在 python3.3 中我也得到了相同的结果:(

标签: javascript python closures


【解决方案1】:

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() 的调用是隐式的(但生成器不能像我的示例中那样无限)。

【讨论】:

    【解决方案2】:

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

    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
    

    【讨论】:

      猜你喜欢
      • 2015-06-14
      • 1970-01-01
      • 2011-11-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多