默认参数和currying:Python与Javascript

时间:2017-01-03 22:59:03

标签: javascript python closures currying

考虑以下问题:我想创建一个函数数组,每个函数只在该数组中打印其索引。在Python中,可以使用

轻松完成
funcs = []
for i in range(5):
   funcs.append(lambda i=i: print(i))
funcs[2]()
# 2

这里我们使用默认参数值作为currying的一种方式(如果我理解正确的术语)。

在ES6之前,Javascript中没有默认参数值,因此必须以不同的方式进行currying。现在我们有了它们,我试图将Python翻译成Javascript:

funcs = []
for (var i=0; i<5; i++) {
   funcs.push(function (i=i) {console.log(i)})
}
# this part pass OK
funcs[2]()
ReferenceError: i is not defined
    at Array.<anonymous> (evalmachine.<anonymous>:3:27)
    at evalmachine.<anonymous>:1:9
    at ContextifyScript.Script.runInThisContext (vm.js:26:33)
    at Object.exports.runInThisContext (vm.js:79:17)
    at run ([eval]:608:19)
    at onRunRequest ([eval]:379:22)
    at onMessage ([eval]:347:17)
    at emitTwo (events.js:106:13)
    at process.emit (events.js:191:7)
    at process.nextTick (internal/child_process.js:752:12)

为什么会失败? Python和Javascript传递默认值的方法有什么区别?

(好的,我知道我可以在这里使用let而不是var,我只是在使用Python几年后学习Javascript并尝试理解它的底层。)

3 个答案:

答案 0 :(得分:2)

你的问题与 默认参数在python与JavaScript中的闭包中绑定时的区别有关。虽然JavaScript和Python都使用后期绑定,但在默认参数的情况下,Python模拟早期绑定,而JavaScript则不然。

话虽如此,如果你打算创建这样的闭包,你也可以利用它们并诚实地抛弃所有参数。

你提到了let的使用,如果你想在for循环中定义函数,这很重要,因为否则funcs[n]将永远是你的迭代器的最大值(由于迟到 - JavaScript闭包的绑定)。

试试这个:

&#13;
&#13;
funcs = [];
for (let i=0; i<5; i++) {
   funcs.push(function () {console.log(i)});
}
funcs[2]();
&#13;
&#13;
&#13;

或者,如果您想遵循在循环中不定义函数的良好实践,您可以在外部定义函数,并使用.bind()传递变量。需要注意的一点是,此方法会将变量与调用.bind()时的值绑定,因此您不必使用let

&#13;
&#13;
funcs = [];
function myFunc(i) {
  console.log(i);
}
for (var i=0; i<5; i++) {
   funcs.push(myFunc.bind(this, i));
}
funcs[2]();
&#13;
&#13;
&#13;

答案 1 :(得分:1)

如果您使用let而不是var,则会为每次迭代获得新的绑定。

funcs = []
for (let i=0; i<5; i++) {
  funcs.push(function (j=i) {console.log(j)})
}

funcs[2];//2

i=i不起作用,因为在ES6参数中可以使用其他参数定义默认值

f = function(a=1, b=a){console.log(b);}
f() // 1

所以解析器感到困惑。

答案 2 :(得分:1)

Javascript和Python在闭包中使用后期绑定。但是,使用默认参数是一种破解,让你在Python中模拟早期绑定,这是有效的,因为默认参数在Python中的函数定义时间进行评估。但是,在Javascript默认参数中,根据文档

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters

  

默认参数在呼叫时被评估,因此不同于在   Python,每次调用函数时都会创建一个新对象。

这是Python中使用的解决方案之一,可以在Javascript中应用于此问题。我尽我所能地将音译成了Javascript。基本上,定义另一个匿名函数,它返回原始函数,并同时应用它。这对我来说非常混乱,在Python中,我总是使用default-argument:

funcs = []
for (var i = 0; i < 5; i++) {
    funcs.push((function (i) {return function() {console.log(i)}})(i))
};
funcs[0]() // 0
funcs[4]() // 4

在Python中:

>>> funcs = []
>>> for i in range(5):
...     funcs.append((lambda i: lambda : print(i))())
...
>>> funcs[0]()
0
>>> funcs[4]()
4

我认为很明显你应该在Javascript中使用.bind,正如其他答案所阐明的那样,而不是这个笨重的解决方案。