Comment décaper une instance namedtuple correctement

J'apprends à utiliser pickle. J'ai créé un objet namedtuple, l'ai ajouté à une liste et j'ai essayé de décaper cette liste. Cependant, j'obtiens l'erreur suivante:

pickle.PicklingError: Can't pickle <class '__main__.P'>: it's not found as __main__.P

J'ai trouvé que si j'exécutais le code sans l'envelopper dans une fonction, cela fonctionnait parfaitement. Est-il une étape supplémentaire nécessaire pour mariner un objet enveloppé à l'intérieur d'une fonction?

Voici mon code:

from collections import namedtuple
import pickle

def pickle_test():
    P = namedtuple("P", "one two three four")
    my_list = []
    abe = P("abraham", "lincoln", "vampire", "hunter")
    my_list.append(abe)
    f = open('abe.pickle', 'w')
    pickle.dump(abe, f)
    f.close()

pickle_test()
40
demandé sur Dirty Penguin 2013-05-04 21:46:07

5 réponses

Créez le tuple nommé en dehors de de la fonction:

from collections import namedtuple
import pickle

P = namedtuple("P", "one two three four")

def pickle_test():
    my_list = []
    abe = P("abraham", "lincoln", "vampire", "hunter")
    my_list.append(abe)
    f = open('abe.pickle', 'w')
    pickle.dump(abe, f)
    f.close()

pickle_test()

Maintenant pickle pouvez le trouver; c'est un module mondial maintenant. Lors du découplage, tout ce que le module pickle A à faire est de localiser à nouveau __main__.P. Dans votre version, P est un local, à la fonction pickle_test(), et ce n'est pas introspectable ou importable.

Il est important de se rappeler que namedtuple() est une fabrique de classes; vous lui donnez des paramètres et il renvoie un objet de classe à partir duquel vous pouvez créer des instances. pickle ne stocke que les données contenues dans les instances, plus une référence de chaîne à la classe d'origine pour reconstruire les instances à nouveau.

60
répondu Martijn Pieters 2013-05-05 10:57:43

Après avoir ajouté ma question en tant que commentaire à la réponse principale, j'ai trouvé un moyen de résoudre le problème de la création dynamique d'un cornichon namedtuple. Ceci est nécessaire dans mon cas car je découvre les champs pendant l'exécution (après une requête DB).

Tout ce que je fais est monkey patch le namedtuple en le déplaçant vers la __main__ module:

def _CreateNamedOnMain(*args):
    import __main__
    namedtupleClass = collections.namedtuple(*args)
    setattr(__main__, namedtupleClass.__name__, namedtupleClass)
    namedtupleClass.__module__ = "__main__"
    return namedtupleClass

En sachant que le nom namedtuple (qui est fourni par args) pourrait écraser un autre membre dans __main__ Si vous n'êtes pas prudent.

7
répondu Chuim 2013-06-26 20:07:31

Alternativement, vous pouvez utiliser cloudpickle ou dill pour la sérialisation:

from collections import namedtuple

import cloudpickle
import dill



def dill_test(dynamic_names):
    P = namedtuple('P', dynamic_names)
    my_list = []
    abe = P("abraham", "lincoln", "vampire", "hunter")
    my_list.append(abe)
    with open('deleteme.cloudpickle', 'wb') as f:
        cloudpickle.dump(abe, f)
    with open('deleteme.dill', 'wb') as f:
        dill.dump(abe, f)


dill_test("one two three four")
2
répondu Peque 2017-09-06 07:21:20

J'ai trouvé cette réponse dans un autre thread. C'est tout au sujet de la dénomination du tuple nommé. Cela a fonctionné pour moi:

group_t =            namedtuple('group_t', 'field1, field2')  # this will work
mismatched_group_t = namedtuple('group_t', 'field1, field2')  # this will throw the error
2
répondu Ruvalcaba 2018-01-16 15:07:37

Le problème ici est que les processus enfants ne sont pas capables d'importer la classe de l'objet-dans ce cas, la classe P-, dans le cas d'un projet multi-modèle, la classe P devrait être importable partout où le processus enfant est utilisé

Une solution de contournement rapide consiste à le rendre importable en l'affectant à globals ()

globals()["P"] = P
0
répondu rachid el kedmiri 2018-06-11 13:25:29