在Python中,如何将YAML映射加载为OrderedDicts?

Eri*_*eth 123 python serialization dictionary yaml pyyaml

我想让PyYAML的加载器将映射(和有序映射)加载到Python 2.7+ OrderedDict类型中,而不是vanilla dict和它当前使用的对列表.

最好的方法是什么?

col*_*fix 140

更新:在python 3.6+中你可能完全不需要OrderedDict由于新的dict实现已经在pypy中使用了一段时间(尽管现在考虑了CPython实现细节).

更新:在python 3.7+中,dict对象的插入顺序保存性质已被声明为Python语言规范的官方部分,请参阅Python 3.7中的新功能.

我喜欢@James的简约解决方案.但是,它会更改默认的全局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)
Run Code Online (Sandbox Code Playgroud)

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

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)
Run Code Online (Sandbox Code Playgroud)

  • @ArneBabenhauserheide我不确定PyPI是否足够上游,但如果您认为,请查看[ruamel.yaml](https://pypi.python.org/pypi/ruamel.yaml)(我是其作者)它确实. (8认同)
  • +1 - 非常感谢你,这给我带来了很多麻烦. (3认同)
  • 这个实现打破了YAML合并标签,BTW (2认同)
  • @Anthon看起来非常好 - 谢谢! (2认同)

Bri*_*sey 54

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)
Run Code Online (Sandbox Code Playgroud)

  • 似乎完美地工作(将iteritems更改为python3中的项目!). (5认同)
  • 这似乎是使用PyYAML的未记录功能(`represent_dict`和`DEFAULT_MAPPING_TAG`).这是因为文档不完整,还是这些功能不受支持,如有更改,恕不另行通知? (5认同)
  • 对这个答案的任何解释? (4认同)
  • 请注意,对于`dict_constructor`,您需要调用`loader.flatten_mapping(node)`或者您将无法加载`<<:*...`(合并语法) (3认同)

wim*_*wim 44

2018年期权:

oyamlPyYAML的替代品,它保留了字典顺序.支持Python 2和Python 3.只是pip install oyaml,导入如下所示:

import oyaml as yaml
Run Code Online (Sandbox Code Playgroud)

在倾倒/装载时,您不再会被拧紧的映射所困扰.

注意:我是oyaml的作者.

  • 这次真是万分感谢!由于某种原因,即使使用 Python 3.8,PyYaml 也不遵守该顺序。oyaml 立即为我解决了这个问题。 (2认同)

Ant*_*hon 25

2015(及更高版本)选项:

ruamel.yaml是PyYAML的替代品(免责声明:我是该软件包的作者).保留映射的顺序是2015年第一版(0.1)中添加的内容之一.它不仅保留了词典的顺序,还保留了注释,锚名称,标签并支持YAML 1.2规范(2009年发布)

规范说不能保证排序,但是当然在YAML文件中有排序,并且适当的解析器可以保持它并透明地生成一个保持排序的对象.您只需要选择正确的解析器,加载器和转储器¹:

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)
Run Code Online (Sandbox Code Playgroud)

会给你:

3: abc
conf:
  10: klm
  3: jig       # h is missing
more:
- what
- else
Run Code Online (Sandbox Code Playgroud)

dataCommentedMap一个类似于dict 的类型,但是有额外的信息一直保留到被转储(包括保留的注释!)


Eri*_*eth 15

注意:有一个库,基于以下答案,它还实现了CLoader和CDumpers:Phynix/yamlloader

我非常怀疑这是最好的方法,但这是我提出的方式,它确实有效.也可作为要点.

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
Run Code Online (Sandbox Code Playgroud)


Ale*_*kov 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)
Run Code Online (Sandbox Code Playgroud)