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

在Python中,如何将YAML映射作为OrderedPicts加载?,python,dictionary,serialization,yaml,pyyaml,Python,Dictionary,Serialization,Yaml,Pyyaml,我想让的loader将映射(和有序映射)加载到Python2.7+类型中,而不是普通的dict及其当前使用的对列表 最好的方法是什么?注意:根据以下答案,有一个库也实现了CLoader和CDumpers: 我非常怀疑这是否是最好的方法,但这是我想出的方法,而且确实有效。也可提供 在Python2.7的For PyYaml安装中,我更新了_init__.py、constructor.py和loader.py。现在支持加载命令的对象对钩子选项。我所做的更改的差异如下 __init__.py $ d

我想让的loader将映射(和有序映射)加载到Python2.7+类型中,而不是普通的dict及其当前使用的对列表


最好的方法是什么?

注意:根据以下答案,有一个库也实现了CLoader和CDumpers:

我非常怀疑这是否是最好的方法,但这是我想出的方法,而且确实有效。也可提供


在Python2.7的For PyYaml安装中,我更新了_init__.py、constructor.py和loader.py。现在支持加载命令的对象对钩子选项。我所做的更改的差异如下

__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)
\uuuu init\uuuuu.py
$diff\uuuu init\uuuuuy.py原件
64c64
def加载(流,加载程序=加载程序):
69c69
加载器=加载器(流)
75c75
def load_all(流,加载器=加载器):
80c80
加载器=加载器(流)
构造函数.py
$diff.py原始构造函数
20,21c20
定义初始化(自):
27,29d25
自构造的_对象={}
>self.recursive_objects={}
129c125
映射={}
400c396
数据={}
595c591
词汇={}
602c598
dictitems=value.get('dictitems',{})
加载器.py
$diff loader.py原版
13c13
定义初始化(自,流):
18c18
BaseConstructor.\uuuuu init\uuuuuuu(自)
23c23
定义初始化(自,流):
28c28
安全构造函数。初始化(自)
33c33
定义初始化(自,流):
38c38
构造函数。初始化(自)

yaml模块允许您指定自定义的“representers”将Python对象转换为文本,并指定“constructor”来反转该过程

_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)
Python>=3.6 在Python3.6+中,似乎默认情况下保留了dict加载顺序,而没有特殊的字典类型。另一方面,默认转储程序按键对字典进行排序。从
pyyaml 5.1
开始,您可以通过传递
sort\u keys=False
来关闭此功能:

a = dict(zip("unsorted", "unsorted"))
s = yaml.safe_dump(a, sort_keys=False)
b = yaml.safe_load(s)

assert list(a.keys()) == list(b.keys())  # True
由于pypy中已经使用了一段时间,所以这可以正常工作。尽管仍在考虑CPython 3.6中的一个实现细节,但从3.7+开始,“dicts的插入顺序保持特性已被宣布为Python语言规范的正式部分”,请参阅

请注意,这仍然没有从PyYAML方面进行记录,所以您不应该将其用于安全关键应用程序

原始答案(与所有已知版本兼容) 我喜欢《詹姆斯》,因为它简单。但是,它更改了默认的全局
yaml.Loader
类,这可能会导致麻烦的副作用。尤其是在编写库代码时,这是一个坏主意。此外,它不能直接与
yaml.safe\u load()配合使用

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

import yaml
from collections import OrderedDict

def ordered_load(stream, Loader=yaml.SafeLoader, 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.SafeDumper, **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)
在每种情况下,您还可以将自定义子类设置为全局,这样就不必在每次调用时重新创建它们。

2015(及更高版本)选项: 是Pyaml的替代品(免责声明:我是该软件包的作者)。保留映射的顺序是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)
将为您提供:

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

data
属于
CommentedMap
类型,它的功能类似于dict,但在转储之前会保留额外的信息(包括保留的注释!)

这里有一个简单的解决方案,可以检查地图中重复的顶级键

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

更新:该库已被弃用,取而代之的是(基于YamLorderedditLoader)

我刚刚发现了一个Python库(),它是根据这个问题的答案创建的,使用起来非常简单:

import yaml
import yamlordereddictloader

datas = yaml.load(open('myfile.yml'), Loader=yamlordereddictloader.Loader)
2018年选项: 是一种保留dict顺序的替代品。Python 2和Python 3都受支持。只需
pip安装oyaml
,并按如下所示导入:

import oyaml as yaml
在转储/加载时,您将不再为错误的映射而烦恼


注意:我是oyaml的作者。

如果您想在错误消息中包含
key\u节点.start\u mark
属性,我看不到任何明显的方法来简化您的中心构造循环。如果您试图利用
OrderedDict
构造函数将接受一组键、值对,那么在生成错误消息时,您将无法访问该细节。有人测试过这一点吗
import oyaml as yaml