You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何使用PyYAML加载与dump YAML并尽可能保留原始样式?

如何在PyYAML中保留字典键顺序的同时维持原始文本块样式?

问题描述

我在用PyYAML处理YAML数据时遇到了一个两难的问题:为了让输出的字典键按我指定的顺序排列,我自定义了dict的representer,但这样做之后,原始YAML里的**文本块(用|-定义的多行内容)**被转义成带\n的单行字符串,可读性直接拉胯。

后来我尝试设置default_style='|'来修复文本块,结果更糟——所有单行字符串都被加了引号,整数也被强制换行,整个YAML格式完全乱套。我就想找个办法,既能保住指定的键顺序,又能让文本块保持原样,其他类型也不受影响,尽量贴近原始输入的样式。

下面是我最开始的代码:

import sys
import yaml

def _represent_dictorder(self, data):
    # Maintains ordering of specific dictionary keys in the YAML output.
    _data = []
    ordering = ['questions', 'tags', 'answers', 'weight', 'date', 'text']
    for key in ordering:
        if key in data:
            _data.append((str(key), data.pop(key)))
    if data:
        _data.extend(data.items())
    return self.represent_mapping(u'tag:yaml.org,2002:map', _data)

yaml.add_representer(dict, _represent_dictorder)

text="""- questions:
  - Lorem ipsum dolor sit amet, consectetur adipiscing elit.
  tags:
    context: curabitur
  answers:
  - weight: 2
    date: 2014-1-19
    text: |-
      1. Mauris lorem magna, auctor et tristique id, fringilla ut metus.
      2. Donec pellentesque elit non felis feugiat, in gravida ex hendrerit.
      3. Mauris quis velit sapien. Nullam blandit, diam et pharetra maximus, mi erat scelerisque turpis, eu vestibulum dui ligula non lectus.
       a. Aenean consectetur eleifend accumsan.
      4. In erat lacus, egestas ut tincidunt ac, congue quis elit. Suspendisse semper purus ac turpis maximus dignissim.
       a. Proin nec neque convallis, placerat odio non, suscipit erat. Nulla nec mattis nibh, accumsan feugiat felis.
      5. Mauris lorem magna, auctor et tristique id, fringilla ut metus.
       a. Morbi non arcu odio. Maecenas faucibus urna et leo euismod placerat.
       b. Nulla facilisi. Pellentesque at pretium nunc.
       c. Ut ipsum nibh, suscipit a pretium eu, eleifend vitae purus.
"""

yaml.dump(yaml.load(text), stream=sys.stdout, default_flow_style=False, indent=4)

解决方案

要解决这个问题,我们需要分开处理两个核心需求:维持字典键顺序智能处理字符串样式,不能用全局的default_style一刀切。

1. 自定义字符串的Representer

我们给PyYAML加一个专门处理字符串的representer,让它自动判断:

  • 如果字符串里有换行符(也就是多行文本块),就用|-块样式输出;
  • 如果是单行字符串,就保持默认的无引号样式(除非有特殊字符需要转义)。

2. 保留原有的字典键顺序逻辑

你之前写的_represent_dictorder逻辑没问题,继续用它来确保指定的键按顺序输出就行。

完整修改后的代码

import sys
import yaml

def _represent_multiline_str(self, data):
    # 只对多行字符串使用|-块样式,单行字符串保持默认格式
    if '\n' in data:
        return self.represent_scalar('tag:yaml.org,2002:str', data, style='|-')
    return self.represent_scalar('tag:yaml.org,2002:str', data)

def _represent_dictorder(self, data):
    # 维持指定字典键的顺序
    _data = []
    ordering = ['questions', 'tags', 'answers', 'weight', 'date', 'text']
    for key in ordering:
        if key in data:
            _data.append((str(key), data.pop(key)))
    if data:
        _data.extend(data.items())
    return self.represent_mapping('tag:yaml.org,2002:map', _data)

# 注册自定义的representer
yaml.add_representer(str, _represent_multiline_str)
yaml.add_representer(dict, _represent_dictorder)

text="""- questions:
  - Lorem ipsum dolor sit amet, consectetur adipiscing elit.
  tags:
    context: curabitur
  answers:
  - weight: 2
    date: 2014-1-19
    text: |-
      1. Mauris lorem magna, auctor et tristique id, fringilla ut metus.
      2. Donec pellentesque elit non felis feugiat, in gravida ex hendrerit.
      3. Mauris quis velit sapien. Nullam blandit, diam et pharetra maximus, mi erat scelerisque turpis, eu vestibulum dui ligula non lectus.
       a. Aenean consectetur eleifend accumsan.
      4. In erat lacus, egestas ut tincidunt ac, congue quis elit. Suspendisse semper purus ac turpis maximus dignissim.
       a. Proin nec neque convallis, placerat odio non, suscipit erat. Nulla nec mattis nibh, accumsan feugiat felis.
      5. Mauris lorem magna, auctor et tristique id, fringilla ut metus.
       a. Morbi non arcu odio. Maecenas faucibus urna et leo euismod placerat.
       b. Nulla facilisi. Pellentesque at pretium nunc.
       c. Ut ipsum nibh, suscipit a pretium eu, eleifend vitae purus.
"""

# 导出时不要设置default_style,让自定义representer处理样式
yaml.dump(yaml.load(text), stream=sys.stdout, default_flow_style=False, indent=4)

为什么这样能解决问题?

  • 精准的字符串样式控制:只有多行文本会被处理成块样式,单行字符串、整数等类型完全不受影响,不会被强制加引号或换行;
  • 独立的键顺序处理:原有的dict representer只负责键的排序,不干涉值的格式;
  • 避免全局样式污染:不用default_style,也就不会把所有类型都改成同一种格式。

这样修改后,导出的YAML会和你原始输入的样式几乎一模一样——文本块保持|-格式,换行符正常显示,单行字符串和整数原样输出,同时字典键也严格按你指定的顺序排列。

内容的提问来源于Stack Exchange,提问作者Cerin

火山引擎 最新活动