通过子流程释放大型python对象

问题描述

如何通过子流程传递和释放大型对象。因此,下面的示例适用于小对象(字典),但是如果其中包含大量数据,则停止工作:

这里有我的工作样本:

return_pickle.py

import pickle
import io
import sys

NUMS = 10
    
sample_obj = {'a':1,'b': [x for x in range(NUMS)]}
d = pickle.dumps(sample_obj)
sys.stdout = io.TextIOWrapper(sys.stdout.detach(),encoding='latin-1')
print(d.decode('latin-1'),end='',flush=True)

unpickle.py

import subprocess
import pickle

proc = subprocess.Popen(["python","return_pickle.py"],stdout=subprocess.PIPE,stderr=subprocess.STDOUT)

output,err = proc.communicate()
data = pickle.loads(output)
print(data)

所以上面的方法按原样工作正常,但是如果我将NUMS更改为100,则会出现_pickle.UnpicklingError: invalid load key,'\x0a'.错误,或者如果我将sample_obj更改为具有字典列表,则该列表是大我会得到同样的错误。我该如何解决?

我在Windows 10机器上使用Python 3.7

解决方法

如果您将protocol=0添加到您的dumps()呼叫中,它将起作用,但这令人费解。 Proto 0是“文本模式”,在改进了较高的pickle协议的许多方面效率低下,但是在Windows上却可以发挥很大作用。

对象的大小并不重要。如果仅将NUMS设置为11,则示例将失败。发生的情况:如果列表中的元素恰好是10,则pickle会生成一个“ opcode”,其字节值为10。但是chr(10) == '\n',在Windows上以文本模式输出的情况下,实现会显示“哦,换行符!我必须将其更改为回车+换行符”。

因此,原始泡菜流中的单个10字节被破坏为13(\r)字节,然后是10(\n)字节。 13最终被放入unpickler正在建立的列表中,然后剩余的10在上下文中根本没有意义。这就是“无效的加载密钥'\ x0a'”消息-0x0a == 10的来源。

当然,还有许多其他方法可以使泡菜流中的值为10 的字节结束,但是如果您以文本模式编写,它们在Windows上都会被破坏。

有多种直接的,与平台无关的方法可以用二进制泡菜来完成此任务,而这比试图将标准输出欺骗成不想要的东西要容易得多。最简单:pickle.dump(obj,f)到一端以二进制写入模式打开的文件,然后另一端只是pickle.load(f)到另一端以二进制模式读取打开的同一文件。

涂百合花;-)

受@flakes的启发,这是一种将stdout诱骗为使用二进制模式的方法,但仅依赖于已记录的可移植API:

import os,sys,pickle
...
with os.fdopen(sys.stdout.fileno(),"wb",closefd=False) as stdout:
    pickle.dump(sample_obj,stdout)

使用匿名操作系统级管道

为显示可能的复杂性,这里使用os.pipe()的情况与此相同。这很烦人,因为OS管道末端在Unix-y系统上是“文件描述符”,而在Windows上实际上是“句柄”。因此,您需要的代码取决于您使用的平台。我只在这里迎合Windows。

writepik.py,由readpik.py调用:

import os,pickle,msvcrt,sys

data = {"d": 1,"L": list(range(50000))}

h = int(sys.argv[1])
d = msvcrt.open_osfhandle(h,0)
with os.fdopen(d,"wb") as dest:
    pickle.dump(data,dest)

因此,它在命令行上传递了一个整数“句柄”,必须将其更改为“文件描述符”,然后将其传递给fdopen(),以创建足以转储泡菜的文件对象。

readpik.py:

import os,subprocess

r,w = os.pipe()
h = msvcrt.get_osfhandle(w)
os.set_handle_inheritable(h,True)
proc = subprocess.Popen(["py","writepik.py",str(h)],close_fds=False)
os.close(w)
with os.fdopen(r,"rb") as src:
    data = pickle.load(src)
print(data)

这有点相反。 os.pipe()返回“文件描述符”,但是要使子进程正确继承一个打开的Windows句柄,我们必须使该句柄可继承而不是文件描述符。因此,我们可以通过get_osfhandle(w)获得足够长的数字“句柄”以将其标记为可继承,并将其值插入到writepik.py的命令行中。

这并不难,但是舞蹈很精致,很容易弄错。

,

如果您不对结果进行字符串化,而是直接将其发布到stdout缓冲区,请在Windows机器上为我工作:

return_pickle.py

import pickle,sys

sample_obj = {'a':1,'b': [x for x in range(100)]}
sys.stdout.buffer.write(pickle.dumps(sample_obj))
import subprocess,pickle

proc = subprocess.Popen(
    ["python","return_pickle.py"],stdout=subprocess.PIPE,stderr=subprocess.DEVNULL,)

output,_ = proc.communicate()
print(pickle.loads(output))

相关问答

依赖报错 idea导入项目后依赖报错,解决方案:https://blog....
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下...
错误1:gradle项目控制台输出为乱码 # 解决方案:https://bl...
错误还原:在查询的过程中,传入的workType为0时,该条件不起...
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct...