通过子进程解开大 python 对象
Unpickle big python object through subprocess
如何通过子进程传递和取消选中大对象。所以我下面的例子适用于小对象(字典),但如果它有大数据就停止工作:
这是我的工作示例:
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 更改为有一个列表字典,如果列表很大,我会得到同样的错误。我该如何解决这个问题?
我在 Python 3.7 和 Windows 10 机器上使用
如果您将 protocol=0
添加到您的 dumps()
调用中,它将起作用,但这非常令人费解。 Proto 0 是“文本模式”,在许多方面效率低下更高的 pickle 协议改进,但在 Windows 上它可以产生巨大的差异。
对象的大小并不重要。如果您只是将 NUMS
设置为 11,您的示例将失败。会发生什么情况:如果列表中的元素恰好是 10,pickle 会生成一个“操作码”,其中一个字节的值为 10。但是 chr(10) == '\n'
,在 Windows 上的文本模式输出中,实现说“哦,一个换行符!我必须将其更改为 carriage-return + 换行符”。
那么 是什么 原始 pickle 流中的单个 10 字节被损坏为 13 (\r
) 字节后跟一个 10 (\n
)字节。 13 最终被放入 unpickler 正在构建的列表中,然后剩下的 10 在上下文中完全没有意义。这就是“无效加载密钥,'\x0a'”消息的来源 - 0x0a == 10
.
当然还有许多其他方法可以将值为 10 的字节 最终放入 pickle 流中,但是如果您以文本模式写入,它们都会在Windows.
有一些直接的 platform-independent 方法可以用二进制 pickle 做到这一点,比试图欺骗 stdout 使其成为它不想要的东西更容易。最简单: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-level 管道
为了显示可能的复杂性,这里使用 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, pickle, msvcrt, 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()
returns “文件描述符”,但是为了让子进程正确继承打开的 Windows 句柄,我们必须使句柄可继承,而不是文件描述符。所以我们通过 get_osfhandle(w)
获得足够长的数字“句柄”以将其标记为可继承并将其值插入 writepik.py.
的命令行
其实不难,但是舞蹈很细腻,很容易出错。
如果您不对结果进行字符串化,而是 post 直接将结果输出到标准输出缓冲区,那么我可以在 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))
如何通过子进程传递和取消选中大对象。所以我下面的例子适用于小对象(字典),但如果它有大数据就停止工作:
这是我的工作示例:
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 更改为有一个列表字典,如果列表很大,我会得到同样的错误。我该如何解决这个问题?
我在 Python 3.7 和 Windows 10 机器上使用
如果您将 protocol=0
添加到您的 dumps()
调用中,它将起作用,但这非常令人费解。 Proto 0 是“文本模式”,在许多方面效率低下更高的 pickle 协议改进,但在 Windows 上它可以产生巨大的差异。
对象的大小并不重要。如果您只是将 NUMS
设置为 11,您的示例将失败。会发生什么情况:如果列表中的元素恰好是 10,pickle 会生成一个“操作码”,其中一个字节的值为 10。但是 chr(10) == '\n'
,在 Windows 上的文本模式输出中,实现说“哦,一个换行符!我必须将其更改为 carriage-return + 换行符”。
那么 是什么 原始 pickle 流中的单个 10 字节被损坏为 13 (\r
) 字节后跟一个 10 (\n
)字节。 13 最终被放入 unpickler 正在构建的列表中,然后剩下的 10 在上下文中完全没有意义。这就是“无效加载密钥,'\x0a'”消息的来源 - 0x0a == 10
.
当然还有许多其他方法可以将值为 10 的字节 最终放入 pickle 流中,但是如果您以文本模式写入,它们都会在Windows.
有一些直接的 platform-independent 方法可以用二进制 pickle 做到这一点,比试图欺骗 stdout 使其成为它不想要的东西更容易。最简单: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-level 管道
为了显示可能的复杂性,这里使用 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, pickle, msvcrt, 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()
returns “文件描述符”,但是为了让子进程正确继承打开的 Windows 句柄,我们必须使句柄可继承,而不是文件描述符。所以我们通过 get_osfhandle(w)
获得足够长的数字“句柄”以将其标记为可继承并将其值插入 writepik.py.
其实不难,但是舞蹈很细腻,很容易出错。
如果您不对结果进行字符串化,而是 post 直接将结果输出到标准输出缓冲区,那么我可以在 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))