【问题标题】:Can PyYAML dump dict items in non-alphabetical order?PyYAML 可以按非字母顺序转储 dict 项目吗?
【发布时间】:2013-05-22 20:06:19
【问题描述】:

我正在使用yaml.dump 输出一个字典。它根据键按字母顺序打印出每个项目。

>>> d = {"z":0,"y":0,"x":0}
>>> yaml.dump( d, default_flow_style=False )
'x: 0\ny: 0\nz: 0\n'

有没有办法控制键/值对的顺序?

在我的特定用例中,反向打印(巧合地)就足够了。不过,为了完整起见,我正在寻找一个显示如何更精确地控制订单的答案。

我看过使用 collections.OrderedDict 但 PyYAML 不(似乎)支持它。我还查看了子类化yaml.Dumper,但我无法弄清楚它是否能够更改项目顺序。

【问题讨论】:

    标签: python dictionary yaml pyyaml


    【解决方案1】:

    如果你将 PyYAML 升级到 5.1 版本,现在它支持转储而不用像这样对键进行排序:

    yaml.dump(data, sort_keys=False)
    

    help(yaml.Dumper)所示,sort_keys默认为True

    Dumper(stream, default_style=None, default_flow_style=False,
      canonical=None, indent=None, width=None, allow_unicode=None,
      line_break=None, encoding=None, explicit_start=None, explicit_end=None,
      version=None, tags=None, sort_keys=True)
    

    (这些作为 kwargs 传递给yaml.dump

    【讨论】:

    • 谢谢@Cooper.Wu 这是一个明显的例子,寻找最新的答案很有帮助。
    • 这对我有用,不需要使用 OrderedDict。
    • 这不会为我保留普通字典的顺序,尝试使用ordereddict,我仍然需要上面的表示器函数才能工作
    【解决方案2】:

    可能有更好的解决方法,但我在文档或源代码中找不到任何内容。


    Python 2(参见 cmets)

    我对@9​​87654321@ 进行了子类化,并让它返回了一个无法排序的项目列表:

    from collections import OrderedDict
    
    class UnsortableList(list):
        def sort(self, *args, **kwargs):
            pass
    
    class UnsortableOrderedDict(OrderedDict):
        def items(self, *args, **kwargs):
            return UnsortableList(OrderedDict.items(self, *args, **kwargs))
    
    yaml.add_representer(UnsortableOrderedDict, yaml.representer.SafeRepresenter.represent_dict)
    

    而且它似乎有效:

    >>> d = UnsortableOrderedDict([
    ...     ('z', 0),
    ...     ('y', 0),
    ...     ('x', 0)
    ... ])
    >>> yaml.dump(d, default_flow_style=False)
    'z: 0\ny: 0\nx: 0\n'
    

    Python 3 或 2(参见 cmets)

    你也可以写一个自定义的表示器,但是我不知道你以后会不会遇到问题,因为我从中去掉了一些样式检查代码:

    import yaml
    
    from collections import OrderedDict
    
    def represent_ordereddict(dumper, data):
        value = []
    
        for item_key, item_value in data.items():
            node_key = dumper.represent_data(item_key)
            node_value = dumper.represent_data(item_value)
    
            value.append((node_key, node_value))
    
        return yaml.nodes.MappingNode(u'tag:yaml.org,2002:map', value)
    
    yaml.add_representer(OrderedDict, represent_ordereddict)
    

    但是,您可以使用原生的OrderedDict 类。

    【讨论】:

    • 非常好,我喜欢你的风格。我将采用第一个解决方案,因为我认为它更清楚一点。无论哪种方式,我都必须重建 dict,并且表示器中的 MappingNode 调用和奇怪的 unicode 字符串使它有点不透明(对我来说!)。谢谢!
    • @mwcz:第一个问题的唯一问题是继承OrderedDict,所以如果它有效,它就有效。
    • 我不确定它是否是我的 Python (3.4) 版本,但这不起作用。我查看了yaml/representer.py:111 的源代码,你可以看到mapping = sorted(mapping)。它使用的是 sorted 内置函数,而不是 UnsortableList 的 .sort() 方法。有什么想法吗?
    • 查看PyYAML 源,事实证明,如果删除了一行,dumper.represent_mapping 会执行此操作。有关详细信息,请参阅我的答案。我认为值得提交一个请求以将其作为一个选项。
    【解决方案3】:

    对于 Python 3.7+,dicts 保留插入顺序。从 PyYAML 5.1.x 开始,您可以禁用键的排序 (#254)。不幸的是,排序键的行为仍然默认为True

    >>> import yaml
    >>> yaml.dump({"b":1, "a": 2})
    'a: 2\nb: 1\n'
    >>> yaml.dump({"b":1, "a": 2}, sort_keys=False)
    'b: 1\na: 2\n'
    

    我的项目oyaml 是 PyYAML 的猴子补丁/插件替代品。默认情况下,它将在所有 Python 版本和 PyYAML 版本中保留字典顺序。

    >>> import oyaml as yaml  # pip install oyaml
    >>> yaml.dump({"b":1, "a": 2})
    'b: 1\na: 2\n'
    

    此外,它会将collections.OrderedDict 子类转储为普通映射,而不是 Python 对象。

    >>> from collections import OrderedDict
    >>> d = OrderedDict([("b", 1), ("a", 2)])
    >>> import yaml
    >>> yaml.dump(d)
    '!!python/object/apply:collections.OrderedDict\n- - - b\n    - 1\n  - - a\n    - 2\n'
    >>> yaml.safe_dump(d)
    RepresenterError: ('cannot represent an object', OrderedDict([('b', 1), ('a', 2)]))
    >>> import oyaml as yaml
    >>> yaml.dump(d)
    'b: 1\na: 2\n'
    >>> yaml.safe_dump(d)
    'b: 1\na: 2\n'
    

    【讨论】:

      【解决方案4】:

      一条线来统治它们:

      yaml.add_representer(dict, lambda self, data: yaml.representer.SafeRepresenter.represent_dict(self, data.items()))
      

      就是这样。最后。经过这么多年和几个小时,强大的represent_dict 已经被dict.items() 而不仅仅是dict 打败了

      它的工作原理如下:

      这是相关的 PyYaml 源码:

          if hasattr(mapping, 'items'):
              mapping = list(mapping.items())
              try:
                  mapping = sorted(mapping)
              except TypeError:
                  pass
          for item_key, item_value in mapping:
      

      为了防止排序,我们只需要一些没有.items()Iterable[Pair] 对象。

      dict_items 是一个完美的候选人。

      这是在不影响 yaml 模块的全局状态的情况下执行此操作的方法:

      #Using a custom Dumper class to prevent changing the global state
      class CustomDumper(yaml.Dumper):
          #Super neat hack to preserve the mapping key order. See https://stackoverflow.com/a/52621703/1497385
          def represent_dict_preserve_order(self, data):
              return self.represent_dict(data.items())    
      
      CustomDumper.add_representer(dict, CustomDumper.represent_dict_preserve_order)
      
      return yaml.dump(component_dict, Dumper=CustomDumper)
      

      【讨论】:

      • dict 添加表示器的方法在 3.7 之前的 Python 版本上无法可靠地工作。请参阅this Q 及其答案。我正在查看您的答案,并对输出是使用dict 而不是OrderedDict 以密钥插入顺序尽管 排序这一事实感到困惑。幸运的是,这里使用的方法可以很容易地适应 OrderedDict 以供需要它的人使用:为 OrderedDict 添加一个表示器而不是 dict,具有相同的实现并且它可以工作。
      【解决方案5】:

      这实际上只是@Blender 答案的补充。如果你查看PyYAML 源代码,在representer.py 模块中,你会发现这个方法:

      def represent_mapping(self, tag, mapping, flow_style=None):
          value = []
          node = MappingNode(tag, value, flow_style=flow_style)
          if self.alias_key is not None:
              self.represented_objects[self.alias_key] = node
          best_style = True
          if hasattr(mapping, 'items'):
              mapping = mapping.items()
              mapping.sort()
          for item_key, item_value in mapping:
              node_key = self.represent_data(item_key)
              node_value = self.represent_data(item_value)
              if not (isinstance(node_key, ScalarNode) and not node_key.style):
                  best_style = False
              if not (isinstance(node_value, ScalarNode) and not node_value.style):
                  best_style = False
              value.append((node_key, node_value))
          if flow_style is None:
              if self.default_flow_style is not None:
                  node.flow_style = self.default_flow_style
              else:
                  node.flow_style = best_style
          return node
      

      如果您只是删除mapping.sort() 行,那么它会保持OrderedDict 中项目的顺序。

      this post 中给出了另一种解决方案。它类似于@Blender,但适用于safe_dump。共同的元素是将 dict 转换为元组列表,因此 if hasattr(mapping, 'items') 检查的结果为 false。

      更新:

      我刚刚注意到 Fedora 项目的 EPEL 存储库有一个名为 python2-yamlordereddictloader 的包,还有一个用于 Python 3 的包。该软件包的上游项目可能是跨平台的。

      【讨论】:

        【解决方案6】:

        你需要做两件事来得到你想要的:

        • 您需要使用 dict 以外的其他内容,因为它不会使项目保持有序
        • 您需要以适当的方式转储该替代方案。¹

        import sys
        import ruamel.yaml
        from ruamel.yaml.comments import CommentedMap
        
        d = CommentedMap()
        d['z'] = 0
        d['y'] = 0
        d['x'] = 0
        
        ruamel.yaml.round_trip_dump(d, sys.stdout)
        

        输出:

        z: 0
        y: 0
        x: 0
        

        ¹ 这是使用 ruamel.yaml 一个 YAML 1.2 解析器完成的,我是它的作者。

        【讨论】:

          【解决方案7】:

          如果使用safe_dump(即dumpDumper=SafeDumper),则调用yaml.add_representer 无效。在这种情况下,需要在 SafeRepresenter 类上显式调用 add_representer 方法:

          yaml.representer.SafeRepresenter.add_representer(
              OrderedDict, ordered_dict_representer
          )
          

          【讨论】:

            【解决方案8】:

            我也在寻找“如何在保留顺序的情况下转储映射?”这个问题的答案。我无法遵循上面给出的解决方案,因为我是 pyyaml 和 python 的新手。在 pyyaml 文档和其他论坛上花了一些时间后,我发现了这一点。

            你可以使用标签

            !!omap

            通过保留顺序来转储映射。如果您想按顺序玩,我认为您必须选择键:值

            以下链接有助于更好地理解。

            https://bitbucket.org/xi/pyyaml/issue/13/loading-and-then-dumping-an-omap-is-broken

            http://yaml.org/type/omap.html

            【讨论】:

            • 能否添加几行代码作为示例?虽然这不是一个仅链接的答案,但如果链接断开,它不会留下很多东西,示例代码对我们懒惰的人来说更方便。 ? 更多关于 StackOverflow 风格的问题请参考:meta.stackexchange.com/questions/8231/…(这个链接不太可能烂掉?)
            【解决方案9】:

            以下设置确保内容在输出中不排序:

            yaml.sort_base_mapping_type_on_output = False
            

            【讨论】:

              猜你喜欢
              • 2014-07-30
              • 1970-01-01
              • 2018-04-13
              • 2017-02-26
              • 2020-11-26
              • 2014-12-17
              • 1970-01-01
              • 1970-01-01
              • 2013-01-10
              相关资源
              最近更新 更多