100 votes

Python mise en conserve après avoir changé le répertoire d'un module

J'ai récemment changé la structure de répertoire de mon programme : avant, j'avais tous mes modules à l'intérieur du dossier "main". Maintenant, je les ai déplacés dans un répertoire nommé d'après le programme, et j'ai placé un __init__.py là-bas pour en faire un package.

Maintenant, j'ai un seul fichier .py dans mon répertoire principal qui est utilisé pour lancer mon programme, ce qui est beaucoup plus propre.

Quoi qu'il en soit, essayer de charger des fichiers picklés des versions précédentes de mon programme échoue. Je reçois l'erreur "ImportError: No module named tools" - ce qui je suppose est parce que mon module était précédemment dans le dossier main, et maintenant il est dans whyteboard.tools, pas simplement tools. Cependant, le code qui importe dans le module tools vit dans le même répertoire que lui, donc je doute qu'il y ait besoin de spécifier un package.

Donc, la structure de mon répertoire de programme ressemble à ceci :

whyteboard-0.39.4

-->whyteboard.py

-->README.txt

-->CHANGELOG.txt

---->whyteboard/

---->whyteboard/__init__.py

---->whyteboard/gui.py

---->whyteboard/tools.py

whyteboard.py lance un bloc de code depuis whyteboard/gui.py, qui démarre l'interface graphique. Ce problème de pickling ne se produisait certainement pas avant la réorganisation du répertoire.

149voto

Alex Martelli Points 330805

Comme le disent les docs de pickle, pour sauvegarder et restaurer une instance de classe (en fait aussi une fonction), vous devez respecter certaines contraintes :

pickle peut sauvegarder et restaurer des instances de classe de manière transparente, cependant la définition de la classe doit être importable et se trouver dans le même module que lors de la sauvegarde de l'objet

whyteboard.tools n'est pas le "même module que" tools (même s'il peut être importé par import tools par d'autres modules dans le même package, il se retrouve dans sys.modules en tant que sys.modules['whyteboard.tools']: c'est absolument crucial, sinon le même module importé par un dans le même package vs un dans un autre package se retrouverait avec des entrées multiples et potentiellement conflictuelles!).

Si vos fichiers pickles sont dans un format bon/avancé (par opposition à l'ancien format ascii qui est le défaut uniquement pour des raisons de compatibilité), les migrer une fois que vous avez effectué ces changements peut en fait ne pas être aussi trivial que "éditer le fichier" (qui est binaire etc...!), malgré ce que suggère une autre réponse. Je suggère plutôt de créer un petit "script de migration des pickles" : laissez-le patcher sys.modules comme ceci... :

import sys
from whyteboard import tools

sys.modules['tools'] = tools

et ensuite charger chaque fichier avec cPickle.load, del sys.modules['tools'], et enregistrer chaque objet chargé de nouveau dans un fichier avec cPickle.dump : cette entrée temporaire supplémentaire dans sys.modules devrait permettre aux pickles de se charger avec succès, puis les re-sauvegarder devrait utiliser le bon nom de module pour les classes des instances (enlever cette entrée supplémentaire devrait s'assurer de cela).

48voto

bossylobster Points 5958

Cela peut être fait avec un "dépickler" personnalisé qui utilise find_class():

import io
import pickle

class RenameUnpickler(pickle.Unpickler):
    def find_class(self, module, name):
        renamed_module = module
        if module == "tools":
            renamed_module = "whyteboard.tools"

        return super(RenameUnpickler, self).find_class(renamed_module, name)

def renamed_load(file_obj):
    return RenameUnpickler(file_obj).load()

def renamed_loads(pickled_bytes):
    file_obj = io.BytesIO(pickled_bytes)
    return renamed_load(file_obj)

Ensuite, vous devriez utiliser renamed_load() au lieu de pickle.load() et renamed_loads() au lieu de pickle.loads().

30voto

Ranch Points 367

Ça m'est arrivé, je l'ai résolu en ajoutant le nouvel emplacement du module à sys.path avant de charger pickle:

import sys
sys.path.append('chemin/vers/tableau_blanc')
f = open("fichier_picklé", "rb")
pickle.load(f)

14voto

Mike McKerns Points 965

pickle sérialise les classes par référence, donc si vous changez l'endroit où la classe se trouve, elle ne sera pas dé-picklée car la classe ne sera pas trouvée. Si vous utilisez dill au lieu de pickle, alors vous pouvez sérialiser les classes par référence ou directement (en sérialisant directement la classe au lieu de son chemin d'importation). Vous pouvez simuler cela assez facilement en modifiant simplement la définition de la classe après un dump et avant un load.

Python 2.7.8 (par défaut, 13 juillet 2014, 02:29:54)
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] sur darwin
Tapez "help", "copyright", "credits" ou "license" pour plus d'informations.
>>> import dill
>>> 
>>> class Foo(object):
...   def bar(self):
...     return 5
... 
>>> f = Foo()
>>> 
>>> _f = dill.dumps(f)
>>> 
>>> class Foo(object):
...   def bar(self, x):
...     return x
... 
>>> g = Foo()
>>> f_ = dill.loads(_f)
>>> f_.bar()
5
>>> g.bar(4)
4

4voto

Luper Rouch Points 5033

Ceci est le comportement normal de pickle, les objets désérialisés doivent avoir leur module de définition importable.

Vous devriez pouvoir modifier le chemin des modules (c'est-à-dire de tools à whyteboard.tools) en éditant les fichiers sérialisés, car ce sont normalement des fichiers texte simples.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X