Js与Python函数赋值与绑定

这几天Gogo在玩Python。
作为咱学的第二门脚本语言,Gogo自然会将它与JavaScript作对比。

函数式编程

函数式编程主要有这么几个特征:

  • 函数可以相互赋值
  • 函数可以嵌套定义
  • 函数可以作为函数的参数、返回值

实际上与面向对象十分相似:

  • 对象可以相互赋值
  • 类可以嵌套定义
  • 对象可以作为函数的参数、返回值

Js和Py都支持函数式编程,也支持面向对象,但它们在函数赋值的地方却有些差异。

函数赋值

Js在函数赋值的地方有个小坑,先来看一段代码。

1
2
3
4
5
6
7
8
9
10
var value = 5;

var obj = {value: 10};
obj.fun = function(){
console.log(this.value);
}
var f = obj.fun;

obj.fun(); // 10
f(); // 5

为什么函数赋值以后结果会发生变化?

其实与this的变化有关。obj.fun这个方法中,this指向的是obj对象,value结果便是10。
而当obj.fun赋值给 f 时,this的值就发生了改变,指向的全局对象(浏览器上就相当于window对象)。
f() 实际上就相当于 window.f()。

想要解决这个问题也十分简单:

1
2
f = obj.fun.bind(obj);
f(); //10

bind 这个方法能绑定 this,上面这段代码也就指定了this指向obj,也就解决了赋值后this变化的问题。

再来看看Python,相对于Js函数赋值的诟病,Python则完全没有这样的困扰,函数赋值后self不会改变。

1
2
3
4
5
6
7
8
9
10
11
12
class A:
def __init__(self, value):
self.value = value

def fun(self):
print(self.value)

obj = A(10)
f = obj.fun

obj.fun() # 10
f() # 10

骤然看上去,似乎Python函数赋值机制更为完善,但其实也有它的问题。

动态添加方法

在Js里,动态给一个对象添加方法是十分简单的。

var obj = {value: 10};
obj.fun = function(){
    console.log(this.value);
}

直接给属性赋值一个函数就好。

在Python中,直接赋值可能会出现问题。原因就在于Python函数中的self是不会改变的,因此必须要手动绑定self到对象。

1
2
3
4
5
6
7
8
9
10
class A:
def __init__(self, value):
self.value = value

def fun(self):
print(self.value)

obj = A(10)
obj.fun = fun # 这是错误的做法
obj.fun() # TypeError: fun() missing 1 required positional argument: 'self'

正确的做法应该这样

1
2
3
4
5
6
7
8
def bind(self, function):
def call(*arg, **argv):
return function(self, *arg, **argv)

self.__setattr__(function.__name__, call)

bind(obj, fun)
obj.fun() # 10

bind这个方法用于绑定一个函数到某个对象。
原理十分简单,调用obj.fun()实际调用的是call函数,call函数把self和参数传递到绑定的fun中,就实现了self的绑定。

小结

Python和JavaScript在函数赋值上的差异,似乎体现了它们不同的设计风格。
Js显得更加自由,Py则显得更加严谨。
当然这只是Gogo初窥两门语言后的想法,更多的有趣之处,得等以后再去挖掘了。