使用pyyaml生成美观输出
我有一个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 个回答
试试这个叫做 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.
如果你能使用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.
所以,正如你所说,如果标量有换行符,你会得到块样式字面量标量;如果标量没有换行符,则没有块样式并且没有引号。