使用pyyaml生成美观输出

21 投票
2 回答
51996 浏览
提问于 2025-04-18 11:05

我有一个Python项目,想用YAML(pyYaml 3.11),主要是因为它看起来“好看”,而且用户在需要的时候可以很方便地用文本编辑器进行修改。不过,我遇到的问题是,当我把YAML文件导入到Python应用程序中(这是必须的),并且需要编辑内容时,写出来的新文件通常没有我开始时的那么好看。

pyyaml的文档写得很糟糕,连dump函数的参数都没有说明。我找到了一些资料,http://dpinte.wordpress.com/2008/10/31/pyaml-dump-option/,但我还是缺少我需要的信息。(我开始查看源代码,但看起来并不太友好。如果在这里找不到解决方案,那我也只能这样了。)

我开始时的文档大概是这样的:

- color green :
     inputs :
        - port thing :
            widget-hint : filename
            widget-help : Select a filename
        - port target_path : 
            widget-hint : path
            value : 'thing' 
     outputs:
        - port value:
             widget-hint : string
     text : |
            I'm lost and I'm found
            and I'm hungry like the wolf.

在用Python加载后(yaml.safe_load(s)),我尝试了几种输出的方法:

>>> print yaml.dump( d3, default_flow_style=False, default_style='' )
- color green:
    inputs:
    - port thing:
        widget-help: Select a filename
        widget-hint: filename
    - port target_path:
        value: thing
        widget-hint: path
    outputs:
    - port value:
        widget-hint: string
    text: 'I''m lost and I''m found

      and I''m hungry like the wolf.

      '
>>> print yaml.dump( d3, default_flow_style=False, default_style='|' )
- "color green":
    "inputs":
    - "port thing":
        "widget-help": |-
          Select a filename
        "widget-hint": |-
          filename
    - "port target_path":
        "value": |-
          thing
        "widget-hint": |-
          path
    "outputs":
    - "port value":
        "widget-hint": |-
          string
    "text": |
      I'm lost and I'm found
      and I'm hungry like the wolf.

理想情况下,我希望“短字符串”不使用引号,就像第一个结果那样。但我希望多行字符串能以块的形式写出来,就像第二个结果那样。我想我根本上是在尝试减少文件中不必要的引号,这样在文本编辑器中编辑时会更方便。

有没有人有这方面的经验呢?

2 个回答

11

试试这个叫做 pyaml 的美化打印工具。它的效果更接近你想要的,不过它会在一些短字符串(里面有空格的)周围加上引号:

>>> print pyaml.dump(d3)
- 'color green':
    inputs:
      - 'port thing':
          widget-help: 'Select a filename'
          widget-hint: filename
      - 'port target_path':
          value: thing
          widget-hint: path
    outputs:
      - 'port value':
          widget-hint: string
    text: |
      I'm lost and I'm found
      and I'm hungry like the wolf.
14

如果你能使用ruamel.yaml(声明一下:我是这个增强版PyYAML的作者),你可以保持原始格式不变(YAML文档存储在文件org.yaml中):

import sys
import ruamel.yaml
from pathlib import Path

file_org = Path('org.yaml')
    
yaml = ruamel.yaml.YAML()
yaml.preserve_quotes = True
data = yaml.load(file_org)
yaml.dump(data, sys.stdout)

这样会得到:

- color green:
    inputs:
    - port thing:
        widget-hint: filename
        widget-help: Select a filename
    - port target_path:
        widget-hint: path
        value: 'thing'
    outputs:
    - port value:
        widget-hint: string
    text: |
      I'm lost and I'm found
      and I'm hungry like the wolf.

你的输入在缩进和格式上不一致,虽然ruamel.yaml在输出上比PyYAML有更多的控制,但你无法完全恢复到原来的样子:

  • 有时候(比如color green :)在值指示符(:)前面有一个空格,有时候没有(比如outputs:)。除了对根级键有特殊控制外,ruamel.yaml总是把值指示符紧挨着键放。
  • 你的根级序列缩进了两个空格,块序列指示符(-)的偏移量为零(这是ruamel.yaml使用的默认设置)。其他的缩进了五个空格,偏移量为三个。ruamel.yaml无法单独或不一致地格式化序列,我建议使用默认设置,因为你的根集合是一个序列。
  • 你的映射有时候缩进了三个空格(比如键color green的值),有时候缩进了两个空格(例如键port target_path的值)。同样,ruamel.yaml无法单独或不一致地格式化这些。
  • 如果你没有在|指示符后面加上块缩进指示符(例如使用|4),你的块样式字面量标量的缩进会比标准的两个空格多。所以这个额外的缩进会丢失。

正如你所看到的,设置yaml.preserv_quotes会保留多余的引号在'thing'周围,因为这不是你想要的,在这个例子中其他地方没有设置。

接下来这个“标准化”了所有三个例子:

import sys
import ruamel.yaml
from pathlib import Path
LT = ruamel.yaml.scalarstring.LiteralScalarString

file_org = Path('org.yaml')
file_plain = Path('plain.yaml')
file_block = Path('block.yaml')

def normalise(d):
    if isinstance(d, dict):
        for k, v in d.items():
             d[k] = normalise(v)
        return d
    if isinstance(d, list):
        for idx, elem in enumerate(d):
            d[idx] = normalise(elem)
        return d
    if not isinstance(d, str):
        return d
    if '\n' in d:
        if isinstance(d, LT):
            return d     # already a block style literal scalar
        return LT(d)
    return str(d)

yaml = ruamel.yaml.YAML()
for fn in [file_org, file_plain, file_block]:
    data = normalise(yaml.load(file_org))
    yaml.dump(data, fn)

assert file_org.read_bytes() == file_plain.read_bytes()
assert file_org.read_bytes() == file_block.read_bytes()
print(file_block.read_text())

这样会得到:

- color green:
    inputs:
    - port thing:
        widget-hint: filename
        widget-help: Select a filename
    - port target_path:
        widget-hint: path
        value: thing
    outputs:
    - port value:
        widget-hint: string
    text: |
      I'm lost and I'm found
      and I'm hungry like the wolf.

所以,正如你所说,如果标量有换行符,你会得到块样式字面量标量;如果标量没有换行符,则没有块样式并且没有引号。

撰写回答