在Python,你怎么可以加载YAML映射关系OrderedDicts?

问题描述 投票:111回答:9

我想获得PyYAML的加载器加载映射(和有序映射)到Python 2.7+ OrderedDict类型,而不是香草dict,它目前使用对的列表。

什么是做到这一点的最好方法是什么?

python dictionary serialization yaml pyyaml
9个回答
138
投票

更新:Python中3.6+你也许并不需要OrderedDict根本原因在于一直在使用的pypy一段时间(虽然被认为CPython的执行细节现在)的new dict implementation

更新:在蟒蛇3.7+,字典对象的插入顺序保存性质已被宣布为Python语言规范的正式组成部分,见What's New In Python 3.7

我喜欢@詹姆斯它的简单solution。然而,它改变默认的全局yaml.Loader类,这可能会导致麻烦的副作用。特别是,编写库代码时,这是一个坏主意。此外,它不直接与yaml.safe_load()工作。

幸运的是,该解决方案可以不费力地改进:

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)

对于序列化,我不知道一个明显的概括,但至少这应该不会有任何的副作用:

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)

53
投票

YAML的模块,允许您指定自定义的“申述”转换Python对象为文本和“建设者”的逆过程。

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

41
投票

2018 option:

oyaml是一个下拉更换为PyYAML它保留字典顺序。双方的Python 2和Python 3的支持。只是pip install oyaml,并导入如下图所示:

import oyaml as yaml

您将不再受拧式映射倾销/加载时恼火。

注:我oyaml的作者。


25
投票

2015 (and later) option:

ruamel.yaml是替代PyYAML下降(免责声明:我是包的作者)。保存映射的顺序是在第一个版本(0.1)早在2015年加入它不仅保留你的字典顺序的事情之一,它也将保留意见,锚的名称,标签和不支持YAML 1.2规范(2009年发布)

该规范指出顺序不能保证,但当然也有在YAML文件排序及相应的解析器可以只坚持到和透明的产生,保持排序的对象。你只需要选择合适解析器,装载机和dumper¹:

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)

会给你:

3: abc
conf:
  10: klm
  3: jig       # h is missing
more:
- what
- else

data的类型是CommentedMap其功能类似于字典,但具有围绕保持,直到被弃置的额外信息(包括保留意见!)


15
投票

注意:有一个图书馆,基于以下的答案,这也实现了CLoader和CDumpers:Phynix/yamlloader

我很怀疑,这是做到这一点的最好办法,但是这是我想出的方式,它的工作。也可as a 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

10
投票

更新:该库有利于yamlloader(这是基于yamlordereddictloader)已过时

我刚刚发现这是基于这个问题的答案创建,使用很简单一个Python库(https://pypi.python.org/pypi/yamlordereddictloader/0.1.1):

import yaml
import yamlordereddictloader

datas = yaml.load(open('myfile.yml'), Loader=yamlordereddictloader.Loader)

4
投票

在我的PyYaml安装的Python 2.7我更新__init__.py,constructor.py和loader.py。现在支持加载命令object_pairs_hook选项。我所做的更改的差异如下。

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

2
投票

有一个PyYAML ticket关于这个问题的5年前开业。它包含了一些相关的链接,包括链接到过这个问题:)我个人抓住gist 317164和修改了它一点点地从Python 2.7版使用OrderedDict,不包含的实现(只是替换from collections import OrderedDict类)。


0
投票

这里有一个简单的解决方案,同时检查你的地图复制的顶级键。

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])
© www.soinside.com 2019 - 2024. All rights reserved.