web-dev-qa-db-fra.com

Python 3 - Pickle peut-il gérer des objets octets supérieurs à 4 Go?

Sur la base de ce comment et de la documentation référencée, Pickle 4.0+ de Python 3.4+ devrait être en mesure de décaper des objets de plus de 4 Go. 

Cependant, lorsque j'utilise python 3.4.3 ou 3.5.10b2 sous Mac OS X 10.10.4, un message d'erreur s'affiche lorsque j'essaie de décaper un tableau d'octets volumineux:

>>> 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

Y at-il un bug dans mon code ou est-ce que je comprends mal la documentation?

49
RandomBits

Voici une solution de contournement simple pour numéro 24658 . Utilisez pickle.loads ou pickle.dumps et coupez l’objet octets en fragments de taille 2**31 - 1 pour le rentrer ou le sortir du fichier.

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)
27
lunguini

Pour résumer ce qui a été répondu dans les commentaires:

Oui, Python peut décaper des objets d'octet supérieurs à 4 Go. L'erreur observée est due à un bogue dans l'implémentation (voir Issue24658 ).

16
Martin Thoma

Voici la solution de contournement complète, bien qu'il semble que pickle.load n'essaye plus de vider un fichier volumineux (je suis sur Python 3.5.2), donc à proprement parler, seul le pickle.dumps a besoin de cela pour fonctionner correctement.

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))
11
Sam Cohan

La lecture d'un fichier par morceaux de 2 Go nécessite deux fois plus de mémoire que nécessaire si la concaténation bytes est effectuée, mon approche de loading pickles est basée sur 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)

Usage:

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

Vous pouvez spécifier le protocole du vidage. Si vous faites pickle.dump(obj,file,protocol=4) cela devrait fonctionner.

1
Yohan Obadia

Avait le même problème et résolu en mettant à niveau vers Python 3.6.8.

Cela semble être le PR qui l'a fait: https://github.com/python/cpython/pull/9937

0
ihopethiswillfi

J'ai également trouvé ce problème, pour résoudre ce problème, je divise le code en plusieurs itérations. Disons que, dans ce cas, j'ai 50 000 données que je dois calculer et analyser. Quand je cours et que je répète directement 50.000, cela me donne "cette erreur". Donc, pour résoudre ce problème, je le fragmente. 

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)
0
raditya gumay