我发现定义函数要求用户定义然后将另一个函数传递给我是一个非常自然的设计模式。例如,
def gradient_descent(x0, grad_f):
x = x0
for _ in range(100):
x -= 0.1 * grad_f(x)
return x
实现通用梯度下降例程;用户所要做的就是为f定义渐变函数。这基本上是scipy.optimize使用的接口,我编写的程序倾向于以类似的方式使用各种函数闭包和动态定义的函数。
然而,我发现自己在利用多处理的并行性方面遇到了一些严重的困难,因为函数无法被腌制。我知道有很多方法可以解决这个问题,但这让我怀疑这样的编程是否是一种“pythonic”的做法。
这是Python中的自然设计模式吗?是否有更好的方法来设计可能需要重构以使用多个流程的程序?
答案 0 :(得分:2)
这完全是Pythonic,但你必须为你的闭包写一个pickler。
Python不会自动为您执行此操作,因为您可能需要几个不同的选项。特别是,你必须决定你想要多远,并假装关闭"。您是否只想复制捕获的值?或者你想复制整个堆栈框架并从中捕获单元格?或者您是否希望实际插入Manager
之类的方式来强制捕获与父级保持同步?
一旦确定要应用的确切规则,就可以编写执行此操作的代码。有关详细信息,请参阅pickle
文档,另请参阅multiprocessing
文档和关联来源,了解其如何以其他方式扩展pickle
。
但好消息是你想要的最有可能是dill
所做的,或者cloudpickle
完全是什么。
一般来说:
dill
尝试尽可能便携,因此您可以将泡菜保存到磁盘并在以后使用它们,即使这意味着您可能不关心的一些事情在封面上略有不同cloudpickle
尽量做到尽可能准确,即使这意味着泡菜不会在你的过程中完全克隆任何东西。如果它们都不是你想要的,你当然可以查看两者的来源,并找出如何做到你想要的。这是一个微不足道的关闭:
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
请注意,它们都会生成一个闭包,捕获一个引用值1的单元格,但cloudpickle
得到的名称完全正确,而dill
没有。如果您尝试pickle.dumps
dill
版本,则会因g
与g
的功能不同而导致错误,而如果您尝试pickle.dumps
1}} cloudpickle
版本,您将在开始时获得与腌制本地对象完全相同的错误。