2017-10-01 3 views
2

J'utilise RangeDict pour créer un dictionnaire contenant des plages. Quand j'utilise Pickle, il est facilement écrit dans un fichier et lu plus tard.Sérialisation d'un RangeDict en utilisant YAML ou JSON en Python

import pickle 
from rangedict import RangeDict 

rngdct = RangeDict() 
rngdct[(1, 9)] = \ 
    {"Type": "A", "Series": "1"} 
rngdct[(10, 19)] = \ 
    {"Type": "B", "Series": "1"} 

with open('rangedict.pickle', 'wb') as f: 
    pickle.dump(rngdct, f) 

Cependant, je veux utiliser YAML (ou JSON si YAML ne fonctionnera pas ...) au lieu de conserves au vinaigre, puisque la plupart des gens semblent détester (et je veux des fichiers lisibles par l'homme afin qu'ils font sens aux gens de les lire)

Fondamentalement, la modification du code pour appeler YAML et ouvrir le fichier en mode 'w', pas 'wb' fait le tour du côté de l'écriture, mais quand je lis le fichier dans un autre script, je reçois ces erreurs:

File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/yaml/constructor.py", line 129, in construct_mapping 
value = self.construct_object(value_node, deep=deep) 
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/yaml/constructor.py", line 61, in construct_object 
"found unconstructable recursive node", node.start_mark) 
yaml.constructor.ConstructorError: found unconstructable recursive node 

Je suis perdu ici. Comment puis-je sérialiser l'objet rangedict et le lire dans sa forme originale?

+0

Je pense que 'NSStock' était une faute de frappe, sinon ajoutez s'il vous plaît sa définition à votre exemple. – Anthon

+0

C'est vrai! Désolé pour cela, j'ai renommé les variables, mais j'ai oublié celui-ci. Merci pour la remarque! @Anthon –

Répondre

0

TL; DR; Passer au fond de cette réponse pour le code de travail


Je suis sûr que certaines personnes détestent pickle, il peut certainement donner quelques maux de tête lorsque le code refactoring (lorsque les classes d'objets marinées se déplacent à différents fichiers). Mais le plus gros problème est que le pickle n'est pas sûr, juste un YAML est dans la façon dont vous l'avez utilisé.

Il est pour intéressant de noter que vous ne pouvez pas décaper le plus lisible protocol level 0 (la valeur par défaut en Python 3 est la version du protocole 3):

pickle.dump (rngdct, f, protocole = 0) sera jeter:

TypeError: a class that defines slots without defining getstate cannot be pickled

en effet, le module/classe est un peu minimaliste, ce qui montre aussi (ou plutôt ne fonctionne pas) RangeDict si vous essayez de le faire:

print(rngdict) 

qui vient imprimer {}

Vous avez probablement utilisé la routine PyYAML dump() (et son correspondant, peu sûr, load()). Et bien que cela puisse vider des classes Python génériques, vous devez réaliser que cela a été implémenté avant ou à peu près en même temps que Python 3.0. (et le support de Python 3 a été implémenté plus tard). Et bien qu'il n'y ait aucune raison qu'un analyseur YAML puisse vider et charger les informations exactes que pickle fait, il ne se connecte pas dans les routines de support pickle (bien qu'il pourrait) et certainement pas dans les informations pour les protocoles de décapage spécifiques Python 3.

De toute façon, sans representer spécifique (et constructeur) pour RangeDict objets, en utilisant YAML ne fait pas vraiment de sens: elle facilite le chargement potentiellement dangereux et votre YAML comprennent tous les détails sordides qui font l'objet efficace .Si vous faites yaml.dump():

!!python/object:rangedict.RangeDict 
_root: &id001 !!python/object/new:rangedict.Node 
    state: !!python/tuple 
    - null 
    - color: 0 
    left: null 
    parent: null 
    r: !!python/tuple [1, 9] 
    right: !!python/object/new:rangedict.Node 
     state: !!python/tuple 
     - null 
     - color: 1 
     left: null 
     parent: *id001 
     r: !!python/tuple [10, 19] 
     right: null 
     value: {Series: '1', Type: B} 
    value: {Series: '1', Type: A} 

Lorsque l'OMI une représentation lisible en YAML serait:

!rangedict 
[1, 9]: 
    Type: A 
    Series: '1' 
[10, 19]: 
    Type: B 
    Series: '1' 

En raison des séquences utilisées en tant que touches, cela ne peut pas être chargé par PyYAML sans modifications majeures à la analyseur Mais heureusement, ces modifications ont été incorporées dans ruamel.yaml (disclaimer: je suis l'auteur de ce paquet), de sorte que « tous » que vous devez faire est de sous-classe RangeDict de fournir des méthodes representer approprié et constructeur (classe):

import io 
import ruamel.yaml 
from rangedict import RangeDict 

class MyRangeDict(RangeDict): 
    yaml_tag = u'!rangedict' 

    def _walk(self, cur): 
     # walk tree left -> parent -> right 
     if cur.left: 
      for x in self._walk(cur.left): 
       yield x 
     yield cur.r 
     if cur.right: 
      for x in self._walk(cur.right): 
       yield x 

    @classmethod 
    def to_yaml(cls, representer, node): 
     d = ruamel.yaml.comments.CommentedMap() 
     for x in node._walk(node._root): 
      d[ruamel.yaml.comments.CommentedKeySeq(x)] = node[x[0]] 
     return representer.represent_mapping(cls.yaml_tag, d) 

    @classmethod 
    def from_yaml(cls, constructor, node): 
     d = cls() 
     for x, y in node.value: 
      x = constructor.construct_object(x, deep=True) 
      y = constructor.construct_object(y, deep=True) 
      d[x] = y 
     return d 


rngdct = MyRangeDict() 
rngdct[(1, 9)] = \ 
    {"Type": "A", "Series": "1"} 
rngdct[(10, 19)] = \ 
    {"Type": "B", "Series": "1"} 

yaml = ruamel.yaml.YAML() 
yaml.register_class(MyRangeDict) # tell the yaml instance about this class 

buf = io.StringIO() 

yaml.dump(rngdct, buf) 
data = yaml.load(buf.getvalue()) 

# test for round-trip equivalence: 
for x in data._walk(data._root): 
    for y in range(x[0], x[1]+1): 
     assert data[y]['Type'] == rngdct[y]['Type'] 
     assert data[y]['Series'] == rngdct[y]['Series'] 

Le buf.getvalue() est exactement la représentation lisible montrée auparavant.

Si vous devez traiter le dumping RangeDict lui-même (à savoir ne peut pas sous-classe parce que vous utilisez une bibliothèque qui a RangeDict hardcoded), vous pouvez ajouter l'attribut et les méthodes de MyRangeDict directement RangeDict par greffage/monkeypatching.

+0

C'est en effet une réponse de travail. Votre bibliothèque YAML fait le tour parfaitement et fournit également un fichier de sortie lisible joliment humain. La partie "équivalence aller-retour" est un peu obscure pour moi, mais elle ne tombe pas dans les exceptions, donc je suppose que mon RangeDict a le bon format/données? –

+0

Cette partie d'équivalence repose sur certains internes, il suffit de tester toutes les plages et s'assure que le 'rngdct' qui a été créé a la même valeur pour la première valeur dans cette plage (' x [0] '), comme' data '. Juste pour vous assurer de ne pas charger() quelque chose et de vous débarrasser de l'erreur, mais de vous retrouver avec quelque chose de complètement différent de ce que vous avez commencé. – Anthon