问题描述
如何通过子流程传递和释放大型对象。因此,下面的示例适用于小对象(字典),但是如果其中包含大量数据,则停止工作:
这里有我的工作样本:
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))