web-dev-qa-db-fra.com

Python décapage après avoir changé le répertoire d'un module

J'ai récemment changé la disposition du répertoire de mon programme: auparavant, j'avais tous mes modules dans le dossier "principal". Maintenant, je les ai déplacés dans un répertoire nommé d'après le programme et placé un __init__.py là pour faire un paquet.

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

Quoi qu'il en soit, essayer de charger des fichiers marinés à partir de versions précédentes de mon programme échoue. Je reçois "ImportError: Aucun module nommé tools" - ce qui, je suppose, est dû au fait que mon module était auparavant dans le dossier principal, et maintenant il se trouve dans whyteboard.tools, et pas simplement des outils simples. Cependant, le code qui est importé dans le module tools vit dans le même répertoire que lui, donc je doute qu'il soit nécessaire de spécifier un package.

Donc, 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 à partir de whyteboard/gui.py, qui déclenche l'interface graphique. Ce problème de décapage ne se produisait certainement pas avant la réorganisation du répertoire.

50
Steven Sproat

Comme documents de pickle disons, afin de sauvegarder et restaurer une instance de classe (en fait une fonction aussi), vous devez respecter certaines contraintes:

pickle peut enregistrer et restaurer des instances de classe de manière transparente, mais la définition de classe doit être importable et vivre dans le même module que lorsque l'objet a été stocké

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 du même package, il se retrouve dans sys.modules comme 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 éventuellement conflictuelles!).

Si vos fichiers pickle sont dans un format bon/avancé (par opposition à l'ancien format ascii qui est le format par défaut uniquement pour des raisons de compatibilité), leur migration une fois que vous avez effectué ces modifications peut en fait pas être tout aussi trivial que "éditer le fichier" (qui est binaire & c ...!), malgré ce qu'une autre réponse suggère. Je suggère que vous fassiez plutôt un petit "script de migration de cornichons": laissez-le patcher sys.modules comme ça...:

import sys
from whyteboard import tools

sys.modules['tools'] = tools

puis cPickle.load chaque fichier, del sys.modules['tools'], et cPickle.dump chaque objet chargé dans le fichier: cette entrée supplémentaire temporaire dans sys.modules devrait laisser les cornichons se charger avec succès, puis les vider à nouveau devrait utiliser le bon nom de module pour les classes des instances (la suppression de cette entrée supplémentaire devrait en être sûre).

65
Alex Martelli

Cela m'est arrivé, résolu en ajoutant le nouvel emplacement du module à sys.path avant de charger le cornichon:

import sys
sys.path.append('path/to/whiteboard')
f = open("pickled_file", "rb")
pickle.load(f)
11
Ranch

Cela peut être fait avec un "décompresseur" 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 devez utiliser renamed_load() au lieu de pickle.load() et renamed_loads() au lieu de pickle.loads().

7
bossylobster

pickle sérialise les classes par référence, donc si vous changez la durée de vie de la classe, elle ne sera pas décryptée car la classe ne sera pas trouvée. Si vous utilisez dill au lieu de pickle, 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 simulez cela assez facilement en changeant simplement la définition de classe après un dump et avant un load.

Python 2.7.8 (default, Jul 13 2014, 02:29:54) 
[GCC 4.2.1 Compatible Apple Clang 4.1 ((tags/Apple/clang-421.11.66))] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> 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
6
Mike McKerns

Il s'agit du comportement normal des cornichons, les objets non décapés doivent avoir leur définition du module importable .

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

5
Luper Rouch