Ich möchte PyYAMLs Loader dazu bringen, Mappings (und geordnete Mappings) in den Python 2.7+ OrderedDict- Typ zu laden , anstelle der Vanille dict
und der Liste der Paare, die derzeit verwendet werden.
Was ist der beste Weg das zu tun?
Ich möchte PyYAMLs Loader dazu bringen, Mappings (und geordnete Mappings) in den Python 2.7+ OrderedDict- Typ zu laden , anstelle der Vanille dict
und der Liste der Paare, die derzeit verwendet werden.
Was ist der beste Weg das zu tun?
Antworten:
Update: In Python 3.6+ benötigen Sie wahrscheinlich OrderedDict
aufgrund der neuen Dikt-Implementierung , die seit einiger Zeit in Pypy verwendet wird, überhaupt nichts (obwohl dies vorerst als CPython-Implementierungsdetail betrachtet wird).
Update: In Python 3.7+ wurde die Beibehaltung der Einfügungsreihenfolge von diktierten Objekten als offizieller Bestandteil der Python-Sprachspezifikation deklariert (siehe Neue Funktionen in Python 3.7) .
Ich mag die Lösung von @James wegen ihrer Einfachheit. Es ändert jedoch die globale Standardklasse yaml.Loader
, was zu störenden Nebenwirkungen führen kann. Insbesondere beim Schreiben von Bibliothekscode ist dies eine schlechte Idee. Es funktioniert auch nicht direkt mit yaml.safe_load()
.
Glücklicherweise kann die Lösung ohne großen Aufwand verbessert werden:
import yaml
from collections import OrderedDict
def ordered_load(stream, Loader=yaml.Loader, object_pairs_hook=OrderedDict):
class OrderedLoader(Loader):
pass
def construct_mapping(loader, node):
loader.flatten_mapping(node)
return object_pairs_hook(loader.construct_pairs(node))
OrderedLoader.add_constructor(
yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
construct_mapping)
return yaml.load(stream, OrderedLoader)
# usage example:
ordered_load(stream, yaml.SafeLoader)
Für die Serialisierung kenne ich keine offensichtliche Verallgemeinerung, aber zumindest sollte dies keine Nebenwirkungen haben:
def ordered_dump(data, stream=None, Dumper=yaml.Dumper, **kwds):
class OrderedDumper(Dumper):
pass
def _dict_representer(dumper, data):
return dumper.represent_mapping(
yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
data.items())
OrderedDumper.add_representer(OrderedDict, _dict_representer)
return yaml.dump(data, stream, OrderedDumper, **kwds)
# usage:
ordered_dump(data, Dumper=yaml.SafeDumper)
Mit dem yaml-Modul können Sie benutzerdefinierte 'Repräsentanten' angeben, um Python-Objekte in Text zu konvertieren, und 'Konstruktoren', um den Prozess umzukehren.
_mapping_tag = yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG
def dict_representer(dumper, data):
return dumper.represent_dict(data.iteritems())
def dict_constructor(loader, node):
return collections.OrderedDict(loader.construct_pairs(node))
yaml.add_representer(collections.OrderedDict, dict_representer)
yaml.add_constructor(_mapping_tag, dict_constructor)
from six import iteritems
und ändern Sie es dann iteritems(data)
so, dass es in Python 2 & 3 gleich gut funktioniert.
represent_dict
und DEFAULT_MAPPING_TAG
) zu verwenden. Liegt dies daran, dass die Dokumentation unvollständig ist, oder werden diese Funktionen nicht unterstützt und können ohne vorherige Ankündigung geändert werden?
dict_constructor
Sie anrufen müssen loader.flatten_mapping(node)
oder nicht laden können <<: *...
(Syntax zusammenführen)
oyaml
ist ein Ersatz für PyYAML, der die Reihenfolge der Diktate beibehält . Sowohl Python 2 als auch Python 3 werden unterstützt. Einfach pip install oyaml
und wie unten gezeigt importieren:
import oyaml as yaml
Sie werden sich beim Dumping / Laden nicht mehr über vermasselte Zuordnungen ärgern.
Hinweis: Ich bin der Autor von Oyaml.
ruamel.yaml ist ein Ersatz für PyYAML (Haftungsausschluss: Ich bin der Autor dieses Pakets). Das Beibehalten der Reihenfolge der Zuordnungen war eines der Dinge, die in der ersten Version (0.1) im Jahr 2015 hinzugefügt wurden. Es behält nicht nur die Reihenfolge Ihrer Wörterbücher bei, sondern auch Kommentare, Ankernamen, Tags und unterstützt YAML 1.2 Spezifikation (veröffentlicht 2009)
Die Spezifikation besagt, dass die Reihenfolge nicht garantiert ist, aber natürlich gibt es eine Reihenfolge in der YAML-Datei, und der entsprechende Parser kann dies einfach beibehalten und transparent ein Objekt generieren, das die Reihenfolge beibehält. Sie müssen nur den richtigen Parser, Lader und Dumper auswählen¹:
import sys
from ruamel.yaml import YAML
yaml_str = """\
3: abc
conf:
10: def
3: gij # h is missing
more:
- what
- else
"""
yaml = YAML()
data = yaml.load(yaml_str)
data['conf'][10] = 'klm'
data['conf'][3] = 'jig'
yaml.dump(data, sys.stdout)
werde dir geben:
3: abc
conf:
10: klm
3: jig # h is missing
more:
- what
- else
data
ist vom Typ, CommentedMap
der wie ein Diktat funktioniert, aber zusätzliche Informationen enthält, die bis zum Ablegen aufbewahrt werden (einschließlich des erhaltenen Kommentars!)
CommentedMap
direkt zu verwenden, aber es funktioniert nicht und OrderedDict
setzt !!omap
überall, was nicht sehr benutzerfreundlich ist.
CommentedMap
with safe=True
in zu speichern YAML
, was nicht funktioniert hat (mithilfe von safe=False
works). Ich hatte auch ein Problem damit, CommentedMap
nicht modifizierbar zu sein, aber ich kann es jetzt nicht reproduzieren ... Ich werde eine neue Frage öffnen, wenn ich erneut auf dieses Problem stoße.
yaml = YAML()
, Sie erhalten den Round-Trip-Parser / Dumper und das ist eine Ableitung des sicheren Parsers / Dumper, der über CommentedMap / Seq usw. Bescheid weiß
Hinweis : Es gibt eine Bibliothek, die auf der folgenden Antwort basiert und auch CLoader und CDumpers implementiert: Phynix / yamlloader
Ich bezweifle sehr, dass dies der beste Weg ist, aber das ist der Weg, den ich mir ausgedacht habe, und es funktioniert. Auch als Kernstück erhältlich .
import yaml
import yaml.constructor
try:
# included in standard lib from Python 2.7
from collections import OrderedDict
except ImportError:
# try importing the backported drop-in replacement
# it's available on PyPI
from ordereddict import OrderedDict
class OrderedDictYAMLLoader(yaml.Loader):
"""
A YAML loader that loads mappings into ordered dictionaries.
"""
def __init__(self, *args, **kwargs):
yaml.Loader.__init__(self, *args, **kwargs)
self.add_constructor(u'tag:yaml.org,2002:map', type(self).construct_yaml_map)
self.add_constructor(u'tag:yaml.org,2002:omap', type(self).construct_yaml_map)
def construct_yaml_map(self, node):
data = OrderedDict()
yield data
value = self.construct_mapping(node)
data.update(value)
def construct_mapping(self, node, deep=False):
if isinstance(node, yaml.MappingNode):
self.flatten_mapping(node)
else:
raise yaml.constructor.ConstructorError(None, None,
'expected a mapping node, but found %s' % node.id, node.start_mark)
mapping = OrderedDict()
for key_node, value_node in node.value:
key = self.construct_object(key_node, deep=deep)
try:
hash(key)
except TypeError, exc:
raise yaml.constructor.ConstructorError('while constructing a mapping',
node.start_mark, 'found unacceptable key (%s)' % exc, key_node.start_mark)
value = self.construct_object(value_node, deep=deep)
mapping[key] = value
return mapping
key_node.start_mark
Attribut in Ihre Fehlermeldung aufnehmen möchten , sehe ich keinen offensichtlichen Weg, um Ihre zentrale Konstruktionsschleife zu vereinfachen. Wenn Sie versuchen, die Tatsache zu nutzen, dass der OrderedDict
Konstruktor eine Iteration von Schlüssel-Wert-Paaren akzeptiert, verlieren Sie beim Generieren der Fehlermeldung den Zugriff auf dieses Detail.
add_constructor
in Ihrer __init__
Methode entfernen .
Update : Die Bibliothek wurde zugunsten des yamlloader (der auf dem yamlordereddictloader basiert) veraltet.
Ich habe gerade eine Python-Bibliothek gefunden ( https://pypi.python.org/pypi/yamlordereddictloader/0.1.1 ), die basierend auf Antworten auf diese Frage erstellt wurde und recht einfach zu verwenden ist:
import yaml
import yamlordereddictloader
datas = yaml.load(open('myfile.yml'), Loader=yamlordereddictloader.Loader)
yodl
schauen Sie sich Github an.
Bei meiner For PyYaml-Installation für Python 2.7 habe ich __init__.py, constructor.py und loader.py aktualisiert. Unterstützt jetzt die Option object_pairs_hook für Ladebefehle. Die verschiedenen Änderungen, die ich vorgenommen habe, sind unten aufgeführt.
__init__.py
$ diff __init__.py Original
64c64
< def load(stream, Loader=Loader, **kwds):
---
> def load(stream, Loader=Loader):
69c69
< loader = Loader(stream, **kwds)
---
> loader = Loader(stream)
75c75
< def load_all(stream, Loader=Loader, **kwds):
---
> def load_all(stream, Loader=Loader):
80c80
< loader = Loader(stream, **kwds)
---
> loader = Loader(stream)
constructor.py
$ diff constructor.py Original
20,21c20
< def __init__(self, object_pairs_hook=dict):
< self.object_pairs_hook = object_pairs_hook
---
> def __init__(self):
27,29d25
< def create_object_hook(self):
< return self.object_pairs_hook()
<
54,55c50,51
< self.constructed_objects = self.create_object_hook()
< self.recursive_objects = self.create_object_hook()
---
> self.constructed_objects = {}
> self.recursive_objects = {}
129c125
< mapping = self.create_object_hook()
---
> mapping = {}
400c396
< data = self.create_object_hook()
---
> data = {}
595c591
< dictitems = self.create_object_hook()
---
> dictitems = {}
602c598
< dictitems = value.get('dictitems', self.create_object_hook())
---
> dictitems = value.get('dictitems', {})
loader.py
$ diff loader.py Original
13c13
< def __init__(self, stream, **constructKwds):
---
> def __init__(self, stream):
18c18
< BaseConstructor.__init__(self, **constructKwds)
---
> BaseConstructor.__init__(self)
23c23
< def __init__(self, stream, **constructKwds):
---
> def __init__(self, stream):
28c28
< SafeConstructor.__init__(self, **constructKwds)
---
> SafeConstructor.__init__(self)
33c33
< def __init__(self, stream, **constructKwds):
---
> def __init__(self, stream):
38c38
< Constructor.__init__(self, **constructKwds)
---
> Constructor.__init__(self)
Hier ist eine einfache Lösung, die auch nach doppelten Schlüsseln der obersten Ebene in Ihrer Karte sucht.
import yaml
import re
from collections import OrderedDict
def yaml_load_od(fname):
"load a yaml file as an OrderedDict"
# detects any duped keys (fail on this) and preserves order of top level keys
with open(fname, 'r') as f:
lines = open(fname, "r").read().splitlines()
top_keys = []
duped_keys = []
for line in lines:
m = re.search(r'^([A-Za-z0-9_]+) *:', line)
if m:
if m.group(1) in top_keys:
duped_keys.append(m.group(1))
else:
top_keys.append(m.group(1))
if duped_keys:
raise Exception('ERROR: duplicate keys: {}'.format(duped_keys))
# 2nd pass to set up the OrderedDict
with open(fname, 'r') as f:
d_tmp = yaml.load(f)
return OrderedDict([(key, d_tmp[key]) for key in top_keys])