web-dev-qa-db-de.com

Wie können Sie in Python YAML-Mappings als OrderedDicts laden?

Ich möchte gerne das Ladeprogramm von PyYAML erhalten, um Mappings (und geordnete Mappings) in den Python 2.7+ OrderedDict Typ zu laden, anstelle der Vanilla dict und der Liste der derzeit verwendeten Paare.

Was ist der beste Weg das zu tun?

105
Eric Naeseth

Note: Es gibt eine Bibliothek, basierend auf der folgenden Antwort, die auch den CLoader und die CDumpers implementiert: Phynix/yamlloader

Ich bezweifle sehr, dass dies der beste Weg ist, dies zu tun, aber so habe ich es gemacht und es funktioniert. Auch erhältlich als Gist .

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
15
Eric Naeseth

Update: In python 3.6+ wird OrderedDict wahrscheinlich überhaupt nicht benötigt, da neue dikt-implementierung das seit einiger Zeit in pypy verwendet wird (obwohl es für den Moment als CPython-Implementierungsdetail betrachtet wird).

Update: In python 3.7+ wurde die Beibehaltung der Einfügereihenfolge von Diktatobjekten deklariert Um ein offizieller Teil der Python Sprachspezifikation zu sein , siehe Was ist neu in Python 3.7 .

Ich mag @James ' Lösung wegen seiner 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)
136
coldfix

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)
52

Option 2018:

oyaml ist ein Drop-In-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 importieren Sie wie folgt:

import oyaml as yaml

Sie werden durch verdrehte Mappings beim Dumping/Laden nicht mehr stören.

Anmerkung: Ich bin der Autor von Oyaml.

26
wim

Option 2015 (und später):

ruamel.yaml ist ein Ersatz für PyYAML (Haftungsausschluss: Ich bin der Autor dieses Pakets). In der ersten Version (0.1) von 2015 wurde die Reihenfolge der Zuordnungen beibehalten. Die Reihenfolge der Wörterbücher wird nicht nur beibehalten, sondern auch Kommentare, Ankernamen, Tags und YAML 1.2 Spezifikation (2009 veröffentlicht)

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 sich nur daran halten und transparent ein Objekt erzeugen, das die Reihenfolge einhält. Sie müssen nur den richtigen Parser, Loader 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 gespeichert werden, bis sie ausgegeben werden (einschließlich des erhaltenen Kommentars!).

19
Anthon

Update: Die Bibliothek wurde zugunsten des yamlloader (der auf dem yamlordereddictloader basiert) verworfen.

Ich habe gerade eine Python-Bibliothek ( https://pypi.python.org/pypi/yamlordereddictloader/0.1.1 ) gefunden, 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)
10
Alex Chekunkov

Bei meiner Installation von For PyYaml für Python 2.7 wurden __init__.py, constructor.py und loader.py aktualisiert. Unterstützt jetzt die object_pairs_hook-Option für Ladebefehle. Der Unterschied der von mir vorgenommenen Änderungen ist unten.

__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)
4
EricGreg

Es gibt ein PyYAML-Ticket zum Thema, das vor 5 Jahren eröffnet wurde. Es enthält einige relevante Links, einschließlich des Links zu dieser Frage :) Ich persönlich habe Gist 317164 ergriffen und ein wenig geändert, um OrderedDict aus Python 2.7 zu verwenden, nicht die enthaltene Implementierung (nur die Klasse ersetzt) mit from collections import OrderedDict).

2
Ilia K.

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])
0
Adam Murphy