如何控制 PyYAML 用于我的数据的标量形式?

Posted

技术标签:

【中文标题】如何控制 PyYAML 用于我的数据的标量形式?【英文标题】:How can I control what scalar form PyYAML uses for my data? 【发布时间】:2012-01-28 06:48:19 【问题描述】:

我有一个具有短字符串属性和长多行字符串属性的对象。我想将短字符串写为 YAML 引用的标量,将多行字符串写为文字标量:

my_obj.short = "Hello"
my_obj.long = "Line1\nLine2\nLine3"

我希望 YAML 看起来像这样:

short: "Hello"
long: |
  Line1
  Line2
  Line3

如何指示 PyYAML 执行此操作?如果我调用yaml.dump(my_obj),它会产生类似字典的输出:

long: 'line1

    line2

    line3

    ', short: Hello

(不知道为什么 long 是这样的双倍行距...)

我可以指示 PyYAML 如何处理我的属性吗?我想同时影响顺序和样式。

【问题讨论】:

【参考方案1】:

爱上@lbt's approach,我得到了这个代码:

import yaml

def str_presenter(dumper, data):
  if len(data.splitlines()) > 1:  # check for multiline string
    return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='|')
  return dumper.represent_scalar('tag:yaml.org,2002:str', data)

yaml.add_representer(str, str_presenter)

# to use with safe_dump:
yaml.representer.SafeRepresenter.add_representer(str, str_presenter)

它使每个多行字符串成为块文字。

我试图避免猴子修补部分。 完全归功于@lbt 和@J.F.Sebastian。

【讨论】:

很好的方法,可以避免显式地标记输入字符串。您可以使用 is_multiline = lambda s: len(s.splitlines()) > 1 自动识别 Unicode 换行符,并且它不会为单行返回 true。 @J.F.Sebastian 很高兴看到这个绝妙的技巧。现在代码看起来好多了。非常感谢! 嗯,style='|' 似乎不会影响 pyyaml @jfs 是的,但是 pyyaml 发射器将在由单引号括起来的两行上打印 test\n,所以如果本练习的目标是在字符串包含换行符时使用块样式而不是引号,那么我们必须像处理多行一样处理这种情况(无论这在技术上是否正确)。 splitlines 相比,简单地测试if '\n' in data 更便宜且效果相同。【参考方案2】:

基于Any yaml libraries in Python that support dumping of long strings as block literals or folded blocks?

import yaml
from collections import OrderedDict

class quoted(str):
    pass

def quoted_presenter(dumper, data):
    return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='"')
yaml.add_representer(quoted, quoted_presenter)

class literal(str):
    pass

def literal_presenter(dumper, data):
    return dumper.represent_scalar('tag:yaml.org,2002:str', data, style='|')
yaml.add_representer(literal, literal_presenter)

def ordered_dict_presenter(dumper, data):
    return dumper.represent_dict(data.items())
yaml.add_representer(OrderedDict, ordered_dict_presenter)

d = OrderedDict(short=quoted("Hello"), long=literal("Line1\nLine2\nLine3\n"))

print(yaml.dump(d))

输出

short: "Hello"
long: |
  Line1
  Line2
  Line3

【讨论】:

有什么方法可以做到这一点,这样它就不会影响全局 yaml 状态,但会影响对 dump() 的单个调用? @JasonS:这是一个很好的单独问题。您可以尝试将您自己的 Dumper 类传递给 yaml.dump,并使用重写的表示标量、表示_dict 方法。 由于某种原因,这在这种情况下不起作用:gist.github.com/retorquere/c43b5394c5e45b4c5b54b46479725e3c。有什么想法吗? 这是一个很好的答案,它对我有用,所以谢谢! ...但我想补充一点,这适用于yaml.dump(),但不适用于yaml.safe_dump()。除非有办法让它与我错过的yaml.safe_dump() 一起工作。 与 safe_dump() 一起使用:yaml.representer.SafeRepresenter.add_representer(OrderedDict, ordered_dict_presenter)【参考方案3】:

我希望任何带有\n 的输入都是块文字。使用yaml/representer.py 中的代码作为我得到的基础:

# -*- coding: utf-8 -*-
import yaml

def should_use_block(value):
    for c in u"\u000a\u000d\u001c\u001d\u001e\u0085\u2028\u2029":
        if c in value:
            return True
    return False

def my_represent_scalar(self, tag, value, style=None):
    if style is None:
        if should_use_block(value):
             style='|'
        else:
            style = self.default_style

    node = yaml.representer.ScalarNode(tag, value, style=style)
    if self.alias_key is not None:
        self.represented_objects[self.alias_key] = node
    return node


a='short': "Hello", 'multiline': """Line1
Line2
Line3
""", 'multiline-unicode': u"""Lêne1
Lêne2
Lêne3
"""

print(yaml.dump(a))
print(yaml.dump(a, allow_unicode=True))
yaml.representer.BaseRepresenter.represent_scalar = my_represent_scalar
print(yaml.dump(a))
print(yaml.dump(a, allow_unicode=True))

输出

multiline: 'Line1

    Line2

    Line3

    ', multiline-unicode: "L\xEAne1\nL\xEAne2\nL\xEAne3\n", short: Hello

multiline: 'Line1

    Line2

    Line3

    ', multiline-unicode: 'Lêne1

    Lêne2

    Lêne3

    ', short: Hello

After override

multiline: |
  Line1
  Line2
  Line3
multiline-unicode: "L\xEAne1\nL\xEAne2\nL\xEAne3\n"
short: Hello

multiline: |
  Line1
  Line2
  Line3
multiline-unicode: |
  Lêne1
  Lêne2
  Lêne3
short: Hello

【讨论】:

【参考方案4】:

你可以使用ruamel.yaml 和它的 RoundTripLoader/Dumper(免责声明:我是那个包的作者)除了做你想做的,它支持 YAML 1.2 规范(从 2009 年开始),并且还有其他一些改进:

import sys
from ruamel.yaml import YAML

yaml_str = """\
short: "Hello"  # does keep the quotes, but need to tell the loader
long: |
  Line1
  Line2
  Line3
folded: >
  some like
  explicit folding
  of scalars
  for readability
"""

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

给予:

short: "Hello"  # does keep the quotes, but need to tell the loader
long: |
  Line1
  Line2
  Line3
folded: >
  some like
  explicit folding
  of scalars
  for readability

(包括评论,与之前在同一列开始)

您也可以从头开始创建此输出,但随后您 确实需要提供额外的信息,例如折叠位置的明确位置。

【讨论】:

以上是关于如何控制 PyYAML 用于我的数据的标量形式?的主要内容,如果未能解决你的问题,请参考以下文章

Python的PyYAML模块详解

PyYaml 结合两个 yaml 文件

如果有“!”,如何使用 PyYAML 解析 YAML在 YAML 中

Python + PyYAML 读取yaml配置文件数据

标量数据

如何使用 Matplotlib 可视化标量 2D 数据?