Closure的应用和替代方案比较

建议从这里下载这篇文章对应的.ipynb文件和相关资源。这样你就能在Jupyter中边阅读,边测试文中的代码。

Closure(闭包) 和相关实现方案

python中, function本身也是object, 可以直接被作为变量传入函数或者被作为结果返回(Java等语言就不能这么干)。 这种灵活性带来很多有趣的应用,其中一个就是closure。 上一个最简单的代码的例子。

closure的三个基本要素

  • 存在一个外层函数(outer)。在outer函数的内部,定义了一个(inner)函数
  • inner函数内部使用outer函数中存在的数据, 这个例子中是a
  • outer函数返回inner函数

下面例举几个可以用到closure的场景

场景一

需要很多功能类似, 但是具体功能又有一定变化的函数。

这时, 可以把outer函数看做是另外一个函数的“制造工厂”, inner函数看做是“工厂”的产出。 通过给outer函数传入参数, 控制制造出的inner函数的具体行为。

例如有一个自动决策系统,可以接受用户自己编写的决策器函数。 这个例子里面

  • 假设决策器只是简单的判断传入数据知否大于某个阈值。
  • 假设框架约定, 传入的决策器函数, 只能接受一个变量。 这个变量是待判断的数值。

懒方案

预先定义一大堆决策器, 每个决策器中的阈值都不同

显然, 这种方法是不现实的。 我们显然不可能穷尽所有可能的阈值并且会造成大量的重复代码。

closure方案

利用closure

这样如果要得到不用阈值的决策器, 只要把阈值传给“工厂”, 让它“生产”我们需要的决策器函数即可。 我们通过传入不同的阈值, 可以得到几百, 几千个不同的决策器函数, 但是”工厂”函数只需要写一个即可。

其它方案

实现一个特定的目的,不一定只有一种方案。 为了实现之前的需求,除了用closure, 当然也可以用其它的方式实现。可以先写一个接受双参数的函数

这个函数不符合只接受“待判断”数值的约定,不过可以用python的partial函数实现。

上面这段代码把threshold参数固定成1, 并且返回只需要接受x的新函数,起到的功能和closure是一样的。

如果不想引入functools这个库, 其实写个lambda函数也是可以起到一样的作用的

场景二

closure的应用

outer函数中的数据, 在每次inner函数被执行的时候, 并不会被清除而是会记忆之前的状态。 因此可以利用这点,创造一个能记录状态的函数。典型的场景是一个计数器。

不错要注意的是,inner函数只能修改outer函数中mutable类型的数据。 如果尝试修改mutable类型的数据, 会导致出错。

object方案

创建一个object, 让object内部的属性去记录自己的状态

通过添加一个__call__特殊method, 可以让上面的counter像函数那样被使用, 这样和closure的例子就更像了。(能够像函数那样被调用的object称为functor)

各种实现方式对性能的影响

有多种实现方案都能实现“记录状态”或是”固定部分行为”, 不同的实现方式对性能的影响如何?以下是一个实验。

code region
0 110000 北京市
1 110100 市辖区
2 110101 东城区
3 110102 西城区
4 110105 朝阳区

为了比较不同版本的代码消耗的时间, 先写一个函数。功能是重复执行函数n次后显示消耗的时间。

接下来准备好五个版本的代码用于比较

双参数版

partial函数版

lambda函数版

closure版

functor版

然后比较一下它们的速度, 各运行1000次

似乎效果一样

lambda lazy binding

lambda是不能配合for循环批量的”制造”函数的

原因是, lambda函数的lazy binding机制, 导致只有lambda函数被调用的时候,才会去查找b的值。这时循环已经结束,b的数值固定为20

但是closure函数并不会出现这个问题

如果一定要用lambda函数实现类似效果的话,可以利用默认值来传入b的数值

partial版的测试

可以看到, partial并没有lazy binding的问题

1 收藏 评论

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部