在Python中使用闭包和动态定义的函数是一种自然的设计模式吗?

RJT*_*JTK 5 python design-patterns functional-programming multiprocessing

我发现定义函数要求用户定义然后将另一个函数传递给我是一个非常自然的设计模式.例如,

def gradient_descent(x0, grad_f):
    x = x0
    for _ in range(100):
        x -= 0.1 * grad_f(x)
    return x
Run Code Online (Sandbox Code Playgroud)

实现通用梯度下降例程; 用户所要做的就是为f定义渐变函数.这基本上是scipy.optimize使用的接口,我编写的程序倾向于以类似的方式使用各种函数闭包和动态定义的函数.

但是,我发现自己在利用多处理的并行性方面遇到了一些严重的困难,因为函数无法被腌制.我知道有很多方法可以解决这个问题,但这让我怀疑这样的编程是否是一种"pythonic"的做事方式.

这是Python中的自然设计模式吗?是否有更好的方法来设计可能需要重构以使用多个流程的程序?

aba*_*ert 3

这完全是Pythonic,但是你必须为你的闭包编写一个pickler。

Python 不会自动为您完成此操作,因为您可能需要一些不同的选项。特别是,您必须决定要“假装封闭”到什么程度。您只想复制捕获的值吗?或者您想复制整个堆栈帧并从中捕获单元格?或者您是否想实际插入 aManager或类似的方式来强制捕获与父级保持同步?

一旦您准确决定要应用什么规则,您就可以编写执行该操作的代码。阅读pickle文档了解详细信息,还可以查看multiprocessing文档和链接的源代码以了解它如何pickle以其他方式扩展。


但好消息是,你想要的很可能就是你想要的dill,或者正是你cloudpickle想要的。

一般来说:

  • dill尝试尽可能便携,因此您可以将泡菜保存到磁盘并稍后使用它们,即使这意味着您可能不关心的某些内容在幕后略有不同。
  • cloudpickle尝试尽可能精确,即使这意味着泡菜除了你的过程的精确克隆之外不起作用。如果它们都不是您想要的,您当然可以查看两者的源代码并找出如何完全按照您想要的方式进行。

这是一个简单的闭包:

def f():
    def g(): return i
    i=1
    return g
g = f()
Run Code Online (Sandbox Code Playgroud)

比较:

>>> pickle.dumps(g)
AttributeError: Can't pickle local object 'f.<locals>.g'
>>> dill.loads(dill.dumps(g))
<function __main__.g>
>>> dill.loads(dill.dumps(g)).__closure__
(<cell at 0x108819618: int object at 0x1009e0980>,)
>>> dill.loads(dill.dumps(g))()
1
>>> cloudpickle.loads(cloudpickle.dumps(g))
<function __main__.f.<locals>.g>
>>> cloudpickle.loads(cloudpickle.dumps(g)).__closure__
(<cell at 0x108819618: int object at 0x1009e0980>,)
>>> cloudpickle.loads(cloudpickle.dumps(g))()
1
Run Code Online (Sandbox Code Playgroud)

请注意,它们最终都会生成一个闭包,该闭包捕获一个引用值 1 的单元格,但cloudpickle名称完全正确,而dill没有。如果您尝试使用pickle.dumps该版本,您将收到有关与不具有相同功能的dill错误,而如果您尝试使用该版本,您将收到与开始时有关腌制本地对象的完全相同的错误。ggpickle.dumpscloudpickle