Python 3 - pickle 可以处理大于 4GB 的字节对象吗?

Posted

技术标签:

【中文标题】Python 3 - pickle 可以处理大于 4GB 的字节对象吗?【英文标题】:Python 3 - Can pickle handle byte objects larger than 4GB? 【发布时间】:2015-10-06 17:16:45 【问题描述】:

基于此comment 和参考文档,Python 3.4+ 中的 Pickle 4.0+ 应该能够pickle 大于 4 GB 的字节对象。

但是,在 Mac OS X 10.10.4 上使用 python 3.4.3 或 python 3.5.0b2 时,当我尝试腌制大字节数组时出现错误:

>>> import pickle
>>> x = bytearray(8 * 1000 * 1000 * 1000)
>>> fp = open("x.dat", "wb")
>>> pickle.dump(x, fp, protocol = 4)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
OSError: [Errno 22] Invalid argument

我的代码中是否存在错误或我误解了文档?

【问题讨论】:

对我来说没问题。 Windows 上的 Python 3.4.1。 在 OS X 上中断。这实际上与泡菜没有任何关系。 open('/dev/null', 'wb').write(bytearray(2**31 - 1)) 有效,但 open('/dev/null', 'wb').write(bytearray(2**3)) 抛出该错误。 Python 2 没有这个问题。 @Blender:什么对你抛出错误对我来说适用于 Python 2.7.10 和 Python 3.4.3(在 OS X、MacPorts 版本上)。 @Blender, @EOL open('/dev/null','wb').write(bytearray(2**31) 在 MacPort 的 python 3.4.3 上也失败了。 错误报告:bugs.python.org/issue24658. 【参考方案1】:

这是issue 24658 的简单解决方法。使用pickle.loadspickle.dumps 并将字节对象分成大小为2**31 - 1 的块以将其放入或取出文件。

import pickle
import os.path

file_path = "pkl.pkl"
n_bytes = 2**31
max_bytes = 2**31 - 1
data = bytearray(n_bytes)

## write
bytes_out = pickle.dumps(data)
with open(file_path, 'wb') as f_out:
    for idx in range(0, len(bytes_out), max_bytes):
        f_out.write(bytes_out[idx:idx+max_bytes])

## read
bytes_in = bytearray(0)
input_size = os.path.getsize(file_path)
with open(file_path, 'rb') as f_in:
    for _ in range(0, input_size, max_bytes):
        bytes_in += f_in.read(max_bytes)
data2 = pickle.loads(bytes_in)

assert(data == data2)

【讨论】:

谢谢。这有很大帮助。一件事:对于write应该for idx in range(0, n_bytes, max_bytes):for idx in range(0, len(bytes_out), max_bytes): @lunguini,对于写块,而不是range(0, n_bytes, max_bytes),应该是range(0, len(bytes_out), max_bytes)吗?我建议这样做的原因是(无论如何,在我的机器上)n_bytes = 1024,但len(bytes_out) = 1062,对于其他使用此解决方案的人,您只使用示例文件的长度,这不一定有用用于真实场景。【参考方案2】:

总结一下cmets的回答:

是的,Python 可以腌制大于 4GB 的字节对象。观察到的错误是由实现中的错误引起的(请参阅Issue24658)。

【讨论】:

这个问题怎么还没解决?疯了 现在是 2018 年,错误仍然存​​在。有谁知道为什么? 已于 2018 年 10 月修复 3.6.8、3.7.2 和 3.8;这个问题仍然悬而未决,因为作者想向后移植到 2.7。在 6 周内,随着 Python 2.x 达到 EOL,这将变得毫无意义。【参考方案3】:

这是完整的解决方法,尽管 pickle.load 似乎不再尝试转储一个大文件(我在 Python 3.5.2 上),所以严格来说只有 pickle.dumps 需要它才能正常工作。

import pickle

class MacOSFile(object):

    def __init__(self, f):
        self.f = f

    def __getattr__(self, item):
        return getattr(self.f, item)

    def read(self, n):
        # print("reading total_bytes=%s" % n, flush=True)
        if n >= (1 << 31):
            buffer = bytearray(n)
            idx = 0
            while idx < n:
                batch_size = min(n - idx, 1 << 31 - 1)
                # print("reading bytes [%s,%s)..." % (idx, idx + batch_size), end="", flush=True)
                buffer[idx:idx + batch_size] = self.f.read(batch_size)
                # print("done.", flush=True)
                idx += batch_size
            return buffer
        return self.f.read(n)

    def write(self, buffer):
        n = len(buffer)
        print("writing total_bytes=%s..." % n, flush=True)
        idx = 0
        while idx < n:
            batch_size = min(n - idx, 1 << 31 - 1)
            print("writing bytes [%s, %s)... " % (idx, idx + batch_size), end="", flush=True)
            self.f.write(buffer[idx:idx + batch_size])
            print("done.", flush=True)
            idx += batch_size


def pickle_dump(obj, file_path):
    with open(file_path, "wb") as f:
        return pickle.dump(obj, MacOSFile(f), protocol=pickle.HIGHEST_PROTOCOL)


def pickle_load(file_path):
    with open(file_path, "rb") as f:
        return pickle.load(MacOSFile(f))

【讨论】:

【参考方案4】:

您可以指定转储的协议。 如果你这样做 pickle.dump(obj,file,protocol=4) 它应该可以工作。

【讨论】:

我所做的是:pickle.dump(data,w,protocol=pickle.HIGHEST_PROTOCOL)。成功了!【参考方案5】:

如果执行bytes 连接,按 2GB 块读取文件所需的内存是所需内存的两倍,我的加载泡菜的方法是基于 bytearray:

class MacOSFile(object):
    def __init__(self, f):
        self.f = f

    def __getattr__(self, item):
        return getattr(self.f, item)

    def read(self, n):
        if n >= (1 << 31):
            buffer = bytearray(n)
            pos = 0
            while pos < n:
                size = min(n - pos, 1 << 31 - 1)
                chunk = self.f.read(size)
                buffer[pos:pos + size] = chunk
                pos += size
            return buffer
        return self.f.read(n)

用法:

with open("/path", "rb") as fin:
    obj = pickle.load(MacOSFile(fin))

【讨论】:

上述代码是否适用于任何平台?如果是这样,上面的代码更像是“FileThatAlsoCanBeLoadedByPickleOnOSX”对吧?只是想理解......这不像我在linux上使用pickle.load(MacOSFile(fin))这会破坏,对吗? @markhor 另外,你会实现write 方法吗?【参考方案6】:

遇到了同样的问题并通过升级到 Python 3.6.8 解决了它。

这似乎是做这件事的公关:https://github.com/python/cpython/pull/9937

【讨论】:

【参考方案7】:

我也发现了这个问题,为了解决这个问题,我将代码分成几个迭代。假设在这种情况下我有 50.000 个数据,我必须计算 tf-idf 并进行 knn 分类。当我运行并直接迭代 50.000 时,它给了我“那个错误”。所以,为了解决这个问题,我把它分块。

tokenized_documents = self.load_tokenized_preprocessing_documents()
    idf = self.load_idf_41227()
    doc_length = len(documents)
    for iteration in range(0, 9):
        tfidf_documents = []
        for index in range(iteration, 4000):
            doc_tfidf = []
            for term in idf.keys():
                tf = self.term_frequency(term, tokenized_documents[index])
                doc_tfidf.append(tf * idf[term])
            doc = documents[index]
            tfidf = [doc_tfidf, doc[0], doc[1]]
            tfidf_documents.append(tfidf)
            print(" from  document ".format(index, doc_length, doc[0]))

        self.save_tfidf_41227(tfidf_documents, iteration)

【讨论】:

以上是关于Python 3 - pickle 可以处理大于 4GB 的字节对象吗?的主要内容,如果未能解决你的问题,请参考以下文章

多处理 Python 3

python3_pickle模块详解

Python pickle模块

使用星图的python多处理和pickle错误?

python序列化模块json和pickle

腌制数据--python(pickle标准库)