【问题标题】:Dumping a dictionary to a YAML file while preserving order在保留顺序的同时将字典转储到 YAML 文件
【发布时间】:2015-10-14 19:22:56
【问题描述】:

我一直在尝试将字典转储到 YAML 文件中。问题是导入 YAML 文件的程序需要特定顺序的关键字。此顺序按字母顺序排列。

import yaml
import os 

baseFile = 'myfile.dat'
lyml = [{'BaseFile': baseFile}]
lyml.append({'Environment':{'WaterDepth':0.,'WaveDirection':0.,'WaveGamma':0.,'WaveAlpha':0.}})

CaseName = 'OrderedDict.yml'
CaseDir = r'C:\Users\BTO\Documents\Projects\Mooring code testen'
CaseFile = os.path.join(CaseDir, CaseName)
with open(CaseFile, 'w') as f:
    yaml.dump(lyml, f, default_flow_style=False)

这会生成一个 *.yml 文件,其格式如下:

- BaseFile: myfile.dat
- Environment:
    WaterDepth: 0.0
    WaveAlpha: 0.0
    WaveDirection: 0.0
    WaveGamma: 0.0

但我想要的是保留订单:

- BaseFile: myfile.dat
- Environment:
    WaterDepth: 0.0
    WaveDirection: 0.0
    WaveGamma: 0.0
    WaveAlpha: 0.0

这可能吗?

【问题讨论】:

    标签: python-3.x dictionary yaml


    【解决方案1】:

    3 年后 - yaml.dump 有一个 sort_keys kwarg,默认设置为 True。将其设置为 False 以不重新排序:

    with open(CaseFile, 'w') as f:
        yaml.dump(lyml, f, default_flow_style=False, sort_keys=False)
    

    【讨论】:

    • 简单干净!
    • TypeError: dump_all() got an unexpected keyword argument 'sort_keys'
    • sort_keys=False 必须默认,浪费了4个小时~___~
    【解决方案2】:

    使用OrderedDict 代替字典。在开始时运行以下设置代码。现在yaml.dump,应该保留订单。更多详情herehere

    def setup_yaml():
      """ https://stackoverflow.com/a/8661021 """
      represent_dict_order = lambda self, data:  self.represent_mapping('tag:yaml.org,2002:map', data.items())
      yaml.add_representer(OrderedDict, represent_dict_order)    
    setup_yaml()
    

    示例:https://pastebin.com/raw.php?i=NpcT6Yc4

    【讨论】:

    • 不知何故我无法在代码块中编写代码。我的意思是,dat使用上述解决方案,无法获得所需的结构?由于 OrderedDict(['Environment',(WaterDepth, 0), (WaveDirection, 0 ,(WaveGamma, 0),(WaveAlpha,0)]) 不正确。有什么建议吗?
    【解决方案3】:

    PyYAML 支持representer 将类实例序列化为 YAML 节点。

    yaml.YAMLObject 使用元类魔法来注册一个构造函数,它将 YAML 节点转换为一个类实例,以及一个表示器,它将一个类实例序列化为一个 YAML 节点。

    在您的代码上方添加以下行:

    def represent_dictionary_order(self, dict_data):
        return self.represent_mapping('tag:yaml.org,2002:map', dict_data.items())
    
    def setup_yaml():
        yaml.add_representer(OrderedDict, represent_dictionary_order)
    
    setup_yaml()
    

    那么你可以使用OrderedDict来保留yaml.dump()中的顺序:

    import yaml
    from collections import OrderedDict
    
    def represent_dictionary_order(self, dict_data):
        return self.represent_mapping('tag:yaml.org,2002:map', dict_data.items())
    
    def setup_yaml():
        yaml.add_representer(OrderedDict, represent_dictionary_order)
    
    setup_yaml()    
    
    dic = OrderedDict()
    
    dic['a'] = 1
    dic['b'] = 2
    dic['c'] = 3
    
    print(yaml.dump(dic))
    # {a: 1, b: 2, c: 3}
    

    【讨论】:

      【解决方案4】:

      您的困难是由于多个级别的假设不正确,并且根据您的 YAML 解析器,可能无法透明地解决。

      在 Python 的 dict 中,键是无序的(至少对于 Python dict 中,它们就不是:

      d = {'WaterDepth':0.,'WaveDirection':0.,'WaveGamma':0.,'WaveAlpha':0.}
      for key in d:
          print key
      

      给予:

      WaterDepth
      WaveGamma
      WaveAlpha
      WaveDirection
      

      如果您希望您的键排序,您可以使用 collections.OrderedDict 类型(或我自己的 ruamel.ordereddict 类型,它在 C 中并且速度快一个数量级以上),并且您必须添加排序的键,或者作为元组列表:

      from ruamel.ordereddict import ordereddict
      # from collections import OrderedDict as ordereddict  # < this will work as well
      d = ordereddict([('WaterDepth', 0.), ('WaveDirection', 0.), ('WaveGamma', 0.), ('WaveAlpha', 0.)])
      for key in d:
          print key
      

      它将按照在源中指定的顺序打印键。

      第二个问题是,即使 Python dict 有一些恰好是您想要的键顺序,YAML 规范确实明确表示映射是无序的,这就是例如PyYAML 实现了 Python dict 到 YAML 映射的转储(反之亦然)。 此外,如果您转储一个 ordereddict 或 OrderedDict,您通常不会得到您想要的纯 YAML 映射,而是一些标记的 YAML 条目。

      由于丢失顺序通常是不可取的,在您的情况下,因为您的读者假设了一些顺序,在我的情况下,因为这使得比较版本变得困难,因为插入/删除后键顺序不一致,我实现了往返一致性在ruamel.yaml 中,您可以这样做:

      import sys
      import ruamel.yaml as yaml
      
      yaml_str = """\
      - BaseFile: myfile.dat
      - Environment:
          WaterDepth: 0.0
          WaveDirection: 0.0
          WaveGamma: 0.0
          WaveAlpha: 0.0
      """
      
      data = yaml.load(yaml_str, Loader=yaml.RoundTripLoader)
      print(data)
      yaml.dump(data, sys.stdout, Dumper=yaml.RoundTripDumper)
      

      它可以准确地为您提供输出结果。 data 用作 dict(`data['Environment'] 也是如此,但在它们下面是更智能的构造,可以保留顺序、cmets、YAML 锚名称等)。您当然可以更改这些(添加/删除键值对),这很容易,但您也可以从头开始构建这些:

      import sys
      import ruamel.yaml as yaml
      from ruamel.yaml.comments import CommentedMap
      
      baseFile = 'myfile.dat'
      lyml = [{'BaseFile': baseFile}]
      lyml.append({'Environment': CommentedMap([('WaterDepth', 0.), ('WaveDirection', 0.), ('WaveGamma', 0.), ('WaveAlpha', 0.)])})
      yaml.dump(data, sys.stdout, Dumper=yaml.RoundTripDumper)
      

      它再次按照您想要的顺序打印带有键的内容。 我发现后者的可读性比从 YAML 字符串开始时要差,但它确实构建 lyml 数据结构的速度更快。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2012-05-05
        • 2021-06-26
        • 2023-04-05
        • 2014-07-20
        • 2020-04-08
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多