格式化 PyYAML 转储()输出

Posted

技术标签:

【中文标题】格式化 PyYAML 转储()输出【英文标题】:Formatting PyYAML dump() output 【发布时间】:2012-12-23 03:04:34 【问题描述】:

我有一个字典列表,我想对其进行序列化:

list_of_dicts = [  'key_1': 'value_a', 'key_2': 'value_b',
                   'key_1': 'value_c', 'key_2': 'value_d',
                  ...
                   'key_1': 'value_x', 'key_2': 'value_y'  ]

yaml.dump(list_of_dicts, file, default_flow_style = False)

产生以下内容:

- key_1: value_a
  key_2: value_b
- key_1: value_c
  key_2: value_d
(...)
- key_1: value_x
  key_2: value_y

但我想得到这个:

- key_1: value_a
  key_2: value_b
                     <-|
- key_1: value_c       | 
  key_2: value_d       |  empty lines between blocks
(...)                  |
                     <-|
- key_1: value_x
  key_2: value_y

PyYAML documentation 非常简短地讨论了 dump() 参数,似乎没有关于这个特定主题的任何内容。

手动编辑文件以添加换行符大大提高了可读性,之后结构仍然可以正常加载,但我不知道如何使转储方法生成它。

一般来说,除了简单的缩进之外,还有什么方法可以更好地控制输出格式?

【问题讨论】:

【参考方案1】:

使用库没有简单的方法来做到这一点(yaml dumper 语法树中的节点对象是被动的,不能发出这个信息),所以我最终得到了

stream = yaml.dump(list_of_dicts, default_flow_style = False)
file.write(stream.replace('\n- ', '\n\n- '))

【讨论】:

谢谢!必须使用类似的东西来格式化列表。 PyYAML 不会在- 之前放置缩进,而我们使用的 YAML 消费库预计会有一些缩进。所以我们不得不做replace('- ', ' - ') 该节点是被动的是真实的但无关紧要,因为节点也不会发出任何其他信息(即ScalarNodes 不会发出自己的值)。 Emitter 确实获得了一个节点的值(在适当的时候)并发出它,如果你在节点上附加额外的信息,并增强相关的 Emitter 方法来处理这些额外的信息(就像我在 ruamel.yaml 中所做的那样),那么就会有绝对不需要做那种基于字符串的粗略的后处理。 @Andrei 使用ruamel.yaml,您可以设置yaml.indent(sequence=3, offset=1) 并在不进行后处理的情况下获得该输出。【参考方案2】:

虽然有点笨拙,但我的目标与 OP 相同。 我通过子类化 yaml.Dumper 解决了它

from yaml import Dumper

class MyDumper(Dumper):

  def write_indent(self):
    indent = self.indent or 0
    if not self.indention or self.column > indent \
        or (self.column == indent and not self.whitespace):
      self.write_line_break()


    ##########$#######################################
    # On the first level of indentation, add an extra
    # newline

    if indent == 2:
      self.write_line_break()

    ##################################################

    if self.column < indent:
      self.whitespace = True
      data = u' '*(indent-self.column)
      self.column = indent
      if self.encoding:
        data = data.encode(self.encoding)
      self.stream.write(data)

你这样称呼它:

print dump(python_dict, default_flow_style=False, width=79, Dumper=MyDumper)

【讨论】:

受此启发,我创建了另一个版本github.com/yaml/pyyaml/issues/127#issuecomment-525800484【参考方案3】:

PyYAML 文档只简要讨论了dump() 参数,因为没有太多要说的。 PyYAML 不提供这种控件。

为了允许在加载的 YAML 中保留这些空(和注释)行,我开始开发 ruamel.yaml 库,它是停滞的 PyYAML 的超集,具有 YAML 1.2 兼容性,添加了许多功能并修复了错误。使用ruamel.yaml,您可以:

import sys
import ruamel.yaml

yaml_str = """\
- key_1: value_a
  key_2: value_b

- key_1: value_c
  key_2: value_d

- key_1: value_x  # a few before this were ellipsed
  key_2: value_y
"""

yaml = ruamel.yaml.YAML()
data = yaml.load(yaml_str)
yaml.dump(data, sys.stdout)

并得到与输入字符串完全相同的输出(包括注释)。

您还可以从头开始构建您想要的输出:

import sys
import ruamel.yaml

yaml = ruamel.yaml.YAML()
list_of_dicts = yaml.seq([  'key_1': 'value_a', 'key_2': 'value_b',
                            'key_1': 'value_c', 'key_2': 'value_d',
                            'key_1': 'value_x', 'key_2': 'value_y'  ])

for idx in range(1, len(list_of_dicts)):
    list_of_dicts.yaml_set_comment_before_after_key(idx, before='\n')

ruamel.yaml.comments.dump_comments(list_of_dicts)
yaml.dump(list_of_dicts, sys.stdout)

使用yaml.seq() 的转换对于创建一个允许通过特殊属性附加空行的对象是必要的。

该库还允许在字符串、int 格式(十六进制、八进制、二进制)和浮点数上保存/轻松设置引号和文字样式。以及用于映射和序列的单独缩进规范(尽管不是针对单个映射或序列)。

【讨论】:

很好的答案,为你自己改进的 yaml 解析器分叉而欢呼,我现在就去尝试一下,因为我现在自己正在研究转储格式问题。不过我突然想到,pyyaml 自从这篇文章以来一直在更新。从那以后,他们在格式方面有所改进吗?或者您的任何贡献是否已合并到 pyyaml 中? 两个问题都没有。我所知道的唯一真正的机会是,如果没有 100% 的输入控制,PyYAML 的 load() 将不再那么危险。它仍然是 YAML 1.1,多个长期存在的错误仍未修复。更改似乎主要是为了与较新版本的 Python 兼容。我认为我的(和所有其他 PR)随着从 bitbucket 到 github 的迁移而被丢弃(如果我在修正 PyYAML 恕我直言,这一努力会更好)。 ruamel.yaml 现在可以在项目指示符 (-) 之后控制额外的换行符:.compact_seq_seq resp。 .compact_seq_map

以上是关于格式化 PyYAML 转储()输出的主要内容,如果未能解决你的问题,请参考以下文章

为 PyYAML 转储的一部分指定样式

强制 pyYAML 持续转储

PyYAML 可以按非字母顺序转储 dict 项目吗?

pyyaml 的漂亮输出

PyYaml - 使用特殊字符(即重音符号)转储 unicode

在 PyYAML 中保存/转储带有注释的 YAML 文件