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

在Python中使用闭包和动态定义的函数是一种自然的设计模式吗?,python,design-patterns,functional-programming,multiprocessing,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 实现一个通用的梯度下降程序;用户所要做的就是定义f的梯度函数。这基本上就是scipy.optimize使用的接口,我编写的程序倾向于以类似的方式使用各种函数闭包和动态定义的函数 然

我发现,对我来说,定义要求用户定义然后传入另一个函数的函数是一种非常自然的设计模式。比如说,

def gradient_descent(x0, grad_f):
    x = x0
    for _ in range(100):
        x -= 0.1 * grad_f(x)
    return x
实现一个通用的梯度下降程序;用户所要做的就是定义f的梯度函数。这基本上就是scipy.optimize使用的接口,我编写的程序倾向于以类似的方式使用各种函数闭包和动态定义的函数

然而,我发现自己在利用多处理的并行性时面临一些严重的困难,因为函数不能被pickle。我知道有很多方法可以解决这个问题,但这让我怀疑这样的编程是否是一种“pythonic”的方式


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

这是完美的Pythonic,但您必须为闭包编写pickler

Python不会自动为您完成这项工作,因为您可能需要一些不同的选项。特别是,你必须决定你想“伪造封闭性”的程度。是否只希望复制捕获的值?还是要复制整个堆栈帧并从中捕获单元格?或者您是否希望实际插入
管理器
或类似工具,以强制捕获与父级保持同步

一旦确定了要应用的规则,就可以编写代码来实现这一点。阅读文档了解详细信息,还可以查看文档和链接的源代码,了解它如何以其他方式扩展
pickle


但好消息是,你想要的很可能是你所做的,或者你所做的

一般而言:

  • dill
    尽量做到便携,这样你就可以将pickle保存到磁盘上,以后再使用,即使这意味着一些你可能不关心的事情在封面下略有不同
  • cloudpickle
    尝试尽可能精确,即使这意味着pickle只能在流程的精确克隆中工作。如果两者都不是您想要的,那么您当然可以查看它们的源代码,并找出如何完全实现您想要的

下面是一个简单的结尾:

def f():
    def g(): return i
    i=1
    return g
g = f()
比较:

>>> 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
pickle.dumps(g) AttributeError:无法pickle本地对象“f..g” >>>钻井装载量(钻井倾倒量(g)) >>>钻孔装载(钻孔倾倒(g))。\u__ (,) >>>钻井负荷(钻井倾卸量(g))() 1. >>>cloudpickle.load(cloudpickle.dumps(g)) >>>cloudpickle.load(cloudpickle.dumps(g))。\uu闭包__ (,) >>>cloudpickle.load(cloudpickle.dumps(g))() 1.
请注意,它们最终都会生成一个闭包,捕获一个引用值1的单元格,但是
cloudpickle
的名称完全正确,而
dill
没有。如果您尝试
pickle.dumps
dill版本,您将得到一个关于
g
g
函数不同的错误,如果您尝试
pickle.dumps
cloudpickle版本,您将得到与开始时完全相同的关于pickle本地对象的错误。

多处理和pickle之间有什么联系?@JohnKugelman-多处理使用pickle将数据传递给工作人员。在本例中,OP希望使用闭包,闭包是函数加上一些局部状态。我认为这是一个有趣的问题。这是正常的和类似于python的,但是闭包是一个有趣的问题。Python通常假定您可以通过文件系统中的模块找到函数。一台机器或一个节点可以运行,只要它们都安装了相同的模块。对于闭包,动态函数数据包含在函数中,我不知道这是如何实现的。这里有一个有趣的解决方案,基本上是使用带有
\uuuuu call\uuuu
的类,而不是闭包。另一种有时更简单的可能是将函数包装在一个全局函数中,该函数需要一些额外的参数,而不是将它们绑定到闭包中,然后传递全局函数和额外参数,或者传递一个将它们绑定到闭包中的
partial
。它看起来不像经过一个局部封闭时那么干净,但实际上它在盖子下更干净(如果合适的话)。