Comment fonctionne la qualification complète automatique des noms de classe, dans Python? [pertinent pour le décapage des objets

StackOverflow https://stackoverflow.com/questions/6009169

Question

(Il est possible de passer directement à la question, plus bas et de sauter l'introduction.)

Il y a une difficulté commune avec des objets Python de décapitation à partir de classes définies par l'utilisateur:

# This is program dumper.py
import pickle

class C(object):
    pass

with open('obj.pickle', 'wb') as f:
    pickle.dump(C(), f)

En fait, essayer de récupérer l'objet d'un autre programme loader.py avec

# This is program loader.py
with open('obj.pickle', 'rb') as f:
    obj = pickle.load(f)

résulte en

AttributeError: 'module' object has no attribute 'C'

En fait, la classe est marinée par son nom ("C"), et le loader.py Le programme ne sait rien sur C. Une solution courante consiste à importer avec

from dumper import C  # Objects of class C can be imported

with open('obj.pickle', 'rb') as f:
    obj = pickle.load(f)

Cependant, cette solution présente quelques inconvénients, y compris le fait que toutes les classes référencées par les objets marinés doivent être importés (il peut y en avoir beaucoup); De plus, l'espace de noms local est pollué par les noms du dumper.py programme.

Maintenant, une solution à cela consiste en des objets pleinement qualifiés avant le décapage:

# New dumper.py program:
import pickle
import dumper  # This is this very program!

class C(object):
    pass

with open('obj.pickle', 'wb') as f:
    pickle.dump(dumper.C(), f)  # Fully qualified class

Décocher avec l'original loader.py Le programme ci-dessus fonctionne désormais directement (pas besoin de faire from dumper import C).

Question: Maintenant, d'autres classes de dumper.py semblent être automatiquement entièrement qualifiés lors du décapage, et j'aimerais savoir comment cela fonctionne et s'il s'agit d'un comportement fiable et documenté:

import pickle
import dumper  # This is this very program!

class D(object):  # New class!
    pass

class C(object):
    def __init__(self):
        self.d = D()  # *NOT* fully qualified

with open('obj.pickle', 'wb') as f:
    pickle.dump(dumper.C(), f)  # Fully qualified pickle class

Maintenant, déboucher avec l'original loader.py Le programme fonctionne également (pas besoin de faire from dumper import C); print obj.d donne un pleinement qualifié classe, que je trouve surprenant:

<dumper.D object at 0x122e130>

Ce comportement est très pratique, car seul l'objet supérieur et mariné doivent être entièrement qualifiés avec le nom du module (dumper.C()). Mais ce comportement est-il fiable et documenté? Comment se fait-il que les cours soient marinés par son nom ("D") mais que le décortiquement décide que le mariné self.d L'attribut est de classe dumper.D (Et pas un peu local D classer)?

PS: la question, raffinée: Je viens de remarquer quelques détails intéressants qui pourraient indiquer une réponse à cette question:

Dans le programme de décapage dumper.py, print self.d tirages <__main__.D object at 0x2af450>, avec le premier dumper.py programme (celui sans import dumper). D'un autre côté, faire import dumper et créer l'objet avec dumper.C() dans dumper.py fait du print self.d imprimer <dumper.D object at 0x2af450>: la self.d L'attribut est automatiquement qualifié par Python! Donc, il semble que le pickle Le module n'a aucun rôle dans le beau comportement de décapitation décrit ci-dessus.

La question est donc vraiment: pourquoi Python se convertit-il D() dans le totalement qualifié dumper.D, dans le deuxième cas? est-ce documenté quelque part?

Était-ce utile?

La solution 2

Voici ce qui se passe: lors de l'importation dumper (ou faire from dumper import C) de l'Intérieur dumper.py, L'ensemble du programme est à nouveau analysé (Cela peut être vu en insérant une impression dans le module). Ce comportement est attendu, car dumper n'est pas un module qui était déjà chargé (__main__ est considéré comme chargé, cependant) - il n'est pas sys.modules.

Comme illustré dans la réponse de Mark, l'importation d'un module qualifie naturellement tous les noms définis dans le module, de sorte que self.d = D() est interprété comme étant en classe dumper.D Lors de la réévaluation du fichier dumper.py (Cela équivaut à l'analyse common.py, dans la réponse de Mark).

Ainsi, le import dumper (ou from dumper import C) L'astuce est expliquée et Pickling est entièrement admissible non seulement C Mais aussi la classe D. Cela facilite le déshabillage par un programme externe!

Cela montre aussi que import dumper fait dumper.py Interprète Python pour analyser le programme deux fois, ce qui n'est ni efficace ni élégant. Pickling des cours dans un programme et les décortiser une autre On est donc probablement mieux réalisé grâce à l'approche décrite dans la réponse de Mark: les classes marinées devraient être dans un module distinct.

Autres conseils

Lorsque vos cours sont définis dans votre module principal, c'est là que Pickle s'attend à les trouver lorsqu'ils ne sont pas parlées. Dans votre premier cas, les classes ont été définies dans le module principal, donc lorsque le chargeur s'exécute, chargeur Le module principal et le cornichon ne trouve pas les classes. Si vous regardez le contenu de obj.pickle, vous verrez alors le nom __main__ Exporté comme espace de noms de vos classes C et D.

Dans votre deuxième cas, Dumper.py s'importe lui-même. Maintenant, vous avez en fait deux ensembles distincts de classes C et D définies: un ensemble __main__ Espace de noms et un ensemble dumper Espace de noms. Vous sérialisez celui dans le dumper Espace de noms (Regardez obj.pickle vérifier).

Pickle tentera d'importer dynamiquement un espace de noms s'il n'est pas trouvé, donc lorsque Loader.py s'exécute cornichon lui-même Importe des classes Dumper.py et Dumper.C et Dumper.D.

Puisque vous avez deux scripts distincts, Dumper.py et Loader.py, il est logique de définir les classes qu'ils partagent dans un module d'importation commun:

Common.py

class D(object):
    pass

class C(object):
    def __init__(self):
        self.d = D()

chargeur.py

import pickle

with open('obj.pickle','rb') as f:
    obj = pickle.load(f)

print obj

dumper.py

import pickle
from common import C

with open('obj.pickle','wb') as f:
    pickle.dump(C(),f)

Notez que même si Dumper.py Dumps C() Dans ce cas, Pickle sait que c'est un common.C objet (voir obj.pickle). Lorsque Loader.py s'exécute, il importera dynamiquement Common.py et réussira à charger l'objet.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top