2017-10-01 1 views
2

Ich verwende RangeDict, um ein Wörterbuch zu erstellen, das Bereiche enthält. Wenn ich Pickle verwende, kann es leicht in eine Datei geschrieben und später gelesen werden.Serialisieren eines RangeDict mit YAML oder JSON in 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) 

Allerdings möchte ich YAML (oder JSON wenn YAML wird nicht funktionieren ...) verwenden, anstatt von Pickle da die meisten der Menschen, die zu hassen scheinen (und ich möchte den Menschen lesbare Dateien, so dass sie Sinn machen die Menschen ihnen)

Grundsätzlich Änderung des Code aufzurufen für yaml und Öffnen der Datei in 'w'-Modus nicht in 'wb' funktioniert der Trick für die Schreibseite, aber wenn ich die Datei in einem anderen Skript lesen, bekomme ich diese Lektüre Fehler:

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 

Ich bin verloren Hier. Wie kann ich das rangedict-Objekt serialisieren und in seiner ursprünglichen Form zurücklesen?

+0

Ich denke, der 'NSStock' war ein Tippfehler, wenn nicht, füge bitte seine Definition zu deinem Beispiel hinzu. – Anthon

+0

Das stimmt! Sorry dafür, ich habe die Variablen umbenannt, aber diese vergessen. Danke für die Bemerkung! @ Anthon –

Antwort

0

TL; DR; Direkt zum Ende der Antwort für die Code-Arbeits


Ich bin sicher, dass einige Leute pickle hassen, es kann sicherlich einigen Kopfschmerzen geben, wenn der Code Refactoring (wenn die Klassen von eingelegten Objekten auf verschiedene Dateien zu verschieben). Aber das größere Problem ist, dass Gurke unsicher ist, nur ein YAML ist in der Art, wie Sie es verwendet haben.

Es ist für interessant, dass Sie nicht auf den besser lesbar protocol level 0 Pickles (3 Standard in Python-Protokoll Version 3) als:

pickle.dump (rngdct, f, protocol = 0) wird werfen:

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

Dies liegt daran, die RangeDict Modul/Klasse ein bisschen minimalistisch ist, was auch zeigt (oder eher nicht), wenn Sie versuchen zu tun:

print(rngdict) 

die gerade {}

Sie wahrscheinlich die dump() Routine PyYAML verwendet gedruckt wird (und die entsprechende, unsicher, load()). Und obwohl dies generische Python-Klassen ausgeben kann, müssen Sie feststellen, dass das vor oder ungefähr zur gleichen Zeit wie Python 3.0 implementiert wurde. (und Python 3 Unterstützung wurde später implementiert). Und obwohl es keinen Grund gibt, warum ein YAML-Parser die genauen Informationen, die pickle sind, ablegen und laden kann, greift er nicht in die pickle Support-Routinen ein (obwohl es möglich war) und sicherlich nicht in die Informationen für die Python 3-spezifischen Beiz-Protokolle.

Jede Art und Weise, ohne einen bestimmten Representer (und Konstruktor) für RangeDict Objekte, YAML verwendet, nicht wirklich keinen Sinn: es macht potenziell unsicheren Laden und Ihre YAML umfassen alle der blutigen Details, die das Objekt effizienter zu gestalten .Wenn Sie yaml.dump() tun:

!!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} 

Wo IMO eine lesbar Darstellung in YAML wäre:

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

Wegen der Sequenzen als Schlüssel verwendet, kann dies nicht durch PyYAML ohne größere Änderungen an der geladen werden Parser. Aber zum Glück haben sich diese Änderungen aufgenommen worden in ruamel.yaml (Disclaimer: Ich bin der Autor dieses Pakets), so „alle“ Sie tun müssen, ist Unterklasse RangeDict geeignet Representer und Konstruktor (Klassen-) Methoden zur Verfügung zu stellen:

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'] 

Die buf.getvalue() ist genau die lesbare Darstellung, die vorher gezeigt wurde.

Wenn Sie mit Dumping RangeDict selbst zu tun haben (das heißt nicht Unterklasse, weil Sie einige Bibliothek verwenden, die RangeDict fest einprogrammiert hat), dann können Sie das Attribut und Methoden der MyRangeDict direkt an RangeDict durch Pfropfen/monkeypatching hinzufügen.

+0

Dies ist in der Tat eine funktionierende Antwort. Ihre YAML-Bibliothek macht den Fehler fehlerfrei und liefert auch eine gut lesbare Ausgabedatei. Der "Rundreise-Äquivalenz" -Teil ist für mich ein bisschen zwielichtig, aber es fällt nicht in Ausnahmen, also denke ich, dass mein RangeDict das richtige Format/Daten hat? –

+0

Dieser Äquivalenzteil beruht auf einigen Interna, er testet nur alle Bereiche und stellt sicher, dass der erzeugte 'rngdct' den gleichen Wert für den ersten Wert in diesem Bereich (' x [0] ') hat, wie die' Daten '. Nur um sicherzustellen, dass Sie nichts "load()" und den Fehler loswerden, aber am Ende etwas völlig anderes haben als das, womit Sie anfangen. – Anthon