【问题标题】:Why can't generators be pickled?为什么发电机不能腌制?
【发布时间】:2011-11-03 01:24:49
【问题描述】:

Python 的 pickle(我在这里说的是标准 Python 2.5/2.6/2.7)不能 pickle 锁、文件对象等。

它也不能 pickle 生成器和 lambda 表达式(或任何其他匿名代码),因为 pickle 实际上只存储名称引用。

对于锁和依赖于操作系统的功能,为什么你不能腌制它们的原因是显而易见的并且是有道理的。

但是为什么不能腌制生成器?


注意:为了清楚起见——我感兴趣的是根本原因(或设计决策中的假设和选择)为什么,而不是“因为它给你一个泡菜错误”。

我意识到这个问题的目标有点宽泛,所以这里有一个经验法则来判断你是否回答了这个问题:“如果提出这些假设,或者允许的发电机类型以某种方式受到更多限制,酸洗发电机会再次工作吗?”

【问题讨论】:

  • 什么时候腌制发电机有意义?
  • @NullUser:不难想象;你正在迭代一个,你想停止你的程序,然后从你离开的地方继续。
  • ...或同时恢复,但来自不同的程序(=序列化也用于网络传输)
  • 我认为这个问题不应该结束。
  • 是的,一开始我实际上误读了他的问题,(希望你能收回近距离投票)而且我是第一个投票重新开放的人。

标签: python generator pickle python-stackless


【解决方案1】:

有很多关于此的可用信息。关于这个问题的“官方说法”,请阅读(closed) Python bugtracker issue

做出决定的人之一的核心推理在this blog上有详细说明:

由于生成器本质上是一个增强的函数,我们需要保存它的字节码,它不能保证在 Python 版本之间向后兼容,以及它的框架,它保存了生成器的状态,例如局部变量,闭包和指令指针。而后者完成起来相当麻烦,因为它基本上需要使整个解释器变得可挑选。因此,任何对酸洗生成器的支持都需要对 CPython 的核心进行大量更改。

现在,如果在生成器的局部变量中出现了 pickle 不支持的对象(例如,文件句柄、套接字、数据库连接等),那么无论是否有任何 pickle 支持我们可能实现的生成器。因此,在这种情况下,您仍然需要提供自定义 __getstate____setstate__ 方法。这个问题使得对生成器的任何酸洗支持都相当有限。

并提到了两个建议的解决方法:

无论如何,如果您需要这样的功能,那么请查看可实现上述所有功能的 Stackless Python。而且由于 Stackless 的解释器是可挑选的,您还可以免费获得进程迁移。这意味着您可以中断一个小任务(Stackless 的绿色线程的名称),对其进行腌制,将腌制发送到另一台机器,取消腌制,恢复小任务,然后瞧,您刚刚迁移了一个进程。这是一个非常酷的功能!

但在我看来,这个问题的最佳解决方案是将生成器重写为简单的迭代器(即,使用__next__ 方法)。迭代器在空间方面很容易和有效地腌制,因为它们的状态是明确的。但是,您仍然需要明确处理表示某些外部状态的对象;你无法解决这个问题。

【讨论】:

  • 很好的答案,谢谢。我找到了“generator_tools”,一个声称可以做到这一点的纯 Python 包。我无法让它工作,所以我猜你(和亚历山大)是对的......
  • metaoptimize.com/blog/2009/12/22/… 上提到了该软件包,它还有另一种解决方法。
  • 您如何在提供答案的同时投票“关闭”?现在我好奇地等待“辩论、争论和扩展讨论”:-)
  • 一开始我误读了您的问题,您无法收回近距离投票。我也是第一个投票决定重新开放的人。
  • Kay,“generator_tools”的作者,说它适用于 Python 2.5,但有一些限制。不幸的是,随着问题的结束,我们可能永远不会听到完整的帐户:(所以我接受你的回答,尽管我的好奇心没有完全满足。
【解决方案2】:

您实际上可以,具体取决于实现。 PyPyStackless Python 都允许这样做(在某种程度上):

Python 2.7.1 (dcae7aed462b, Aug 17 2011, 09:46:15)
[PyPy 1.6.0 with GCC 4.0.1] on darwin
Type "help", "copyright", "credits" or "license" for more information.
And now for something completely different: ``Not your usual analyses.''
>>>> import pickle
>>>> gen = (x for x in range(100))
>>>> next(gen)
0
>>>> pickled = pickle.dumps(gen)
>>>> next(pickle.loads(pickled))
1

在 CPython 中,还可以创建一个 iterator object 来模拟一个可选择的生成器。

【讨论】:

  • @Radim 它回答了我的问题。
猜你喜欢
  • 1970-01-01
  • 2023-03-14
  • 1970-01-01
  • 2014-12-04
  • 2016-02-21
  • 1970-01-01
  • 1970-01-01
  • 2019-08-09
  • 1970-01-01
相关资源
最近更新 更多