PyYAML 单引号中的单个字符串

A single string in single quotes with PyYAML

当我在 Python 中使用 PyYAML 编辑 YAML 文件时,我所有的字符串值都被保存回原始文件而不带引号。

one: valueOne
two: valueTwo
three: valueThree

我希望其中一个字符串用单引号引起来:

one: valueOne
two: valueTwo
three: 'valueThree'

更改 yaml_dump 中的 default_style 参数会影响整个文件,这是不需要的。我想过在我想要包围的字符串的开头和结尾添加单引号:

valueThreeVariable = "'" + valueThreeVariable + "'"

然而,这最终会得到一个如下所示的转储 YAML:

one: valueOne
two: valueTwo
three: '''valueThree'''

我尝试过使用 unicode 或原始字符串以各种方式转义单引号,但都无济于事。 我如何才能使我的 YAML 值中的一个成为用单引号括起来的字符串?

您可以将此类功能移植到 PyYAML 上,但这并不容易。 three 映射中的值必须是不同于普通字符串的 class 的某个实例,否则 YAML 转储器不知道它必须做一些特殊的事情并且该实例被转储为带引号的字符串。在加载带有单引号的标量时,需要创建为这个 class 的实例。除此之外,您可能不希望 dict/mapping 的密钥像 PyYAML 默认情况下那样乱码。

我在我的 PyYAML 导数 ruamel.yaml 中为块样式标量做了类似于上面的事情:

import ruamel.yaml

yaml_str = """\
one: valueOne
two: valueTwo
three: |-
  valueThree
"""

data = ruamel.yaml.round_trip_load(yaml_str)
assert ruamel.yaml.round_trip_dump(data) == yaml_str

不会抛出断言错误。


要从转储程序开始,您可以 "convert" valueThree 字符串:

import ruamel.yaml
from ruamel.yaml.scalarstring import ScalarString

yaml_str = """\
one: valueOne
two: valueTwo
three: 'valueThree'
"""

class SingleQuotedScalarString(ScalarString):
    def __new__(cls, value):
        return ScalarString.__new__(cls, value)

data = ruamel.yaml.round_trip_load(yaml_str)
data['three'] = SingleQuotedScalarString(data['three'])

但这不能被转储,因为转储者不知道 SingleQuotedScalarString。您可以通过不同的方式解决该问题,以下扩展 ruamel.yaml's RoundTripRepresenter class:

from ruamel.yaml.representer import RoundTripRepresenter
import sys

def _represent_single_quoted_scalarstring(self, data):
    tag = None
    style = "'"
    if sys.version_info < (3,) and not isinstance(data, unicode):
        data = unicode(data, 'ascii')
    tag = u'tag:yaml.org,2002:str'
    return self.represent_scalar(tag, data, style=style)

RoundTripRepresenter.add_representer(
    SingleQuotedScalarString,
    _represent_single_quoted_scalarstring)

assert ruamel.yaml.round_trip_dump(data) == yaml_str

再一次没有抛出错误。以上内容原则上可以在 PyYAML 和 safe_load/safe_dump 中完成,但您需要编写代码来保留键顺序以及一些基本功能。 (除此之外,PyYAML 仅支持旧的 YAML 1.1 标准,不支持 2009 年的 YAML 1.2 标准)。

要在不使用显式 data['three'] = SingleQuotedScalarString(data['three']) 转换的情况下进行加载,您可以在调用 ruamel.yaml.round_trip_load() 之前添加以下内容:

from ruamel.yaml.constructor import RoundTripConstructor
from ruamel.yaml.nodes import ScalarNode
from ruamel.yaml.compat import text_type

def _construct_scalar(self, node):
    if not isinstance(node, ScalarNode):
        raise ConstructorError(
            None, None,
            "expected a scalar node, but found %s" % node.id,
            node.start_mark)

    if node.style == '|' and isinstance(node.value, text_type):
        return PreservedScalarString(node.value)
    elif node.style == "'" and isinstance(node.value, text_type):
        return SingleQuotedScalarString(node.value)
    return node.value

RoundTripConstructor.construct_scalar = _construct_scalar

有不同的方法可以完成上述操作,包括 class 对 RoundTripConstructor class 的子class,但实际要更改的方法很小,很容易修补。


结合以上所有内容并稍微清理一下,您会得到:

import ruamel.yaml
from ruamel.yaml.scalarstring import ScalarString
from ruamel.yaml.representer import RoundTripRepresenter
from ruamel.yaml.constructor import RoundTripConstructor
from ruamel.yaml.nodes import ScalarNode
from ruamel.yaml.compat import text_type, PY2


class SingleQuotedScalarString(ScalarString):
    def __new__(cls, value):
        return ScalarString.__new__(cls, value)


def _construct_scalar(self, node):
    if not isinstance(node, ScalarNode):
        raise ConstructorError(
            None, None,
            "expected a scalar node, but found %s" % node.id,
            node.start_mark)

    if node.style == '|' and isinstance(node.value, text_type):
        return PreservedScalarString(node.value)
    elif node.style == "'" and isinstance(node.value, text_type):
        return SingleQuotedScalarString(node.value)
    return node.value

RoundTripConstructor.construct_scalar = _construct_scalar


def _represent_single_quoted_scalarstring(self, data):
    tag = None
    style = "'"
    if PY2 and not isinstance(data, unicode):
        data = unicode(data, 'ascii')
    tag = u'tag:yaml.org,2002:str'
    return self.represent_scalar(tag, data, style=style)

RoundTripRepresenter.add_representer(
    SingleQuotedScalarString,
    _represent_single_quoted_scalarstring)


yaml_str = """\
one: valueOne
two: valueTwo
three: 'valueThree'
"""

data = ruamel.yaml.round_trip_load(yaml_str)
assert ruamel.yaml.round_trip_dump(data) == yaml_str

它仍然运行而没有断言错误,即转储输出等于输入。如前所述,您可以在 PyYAML 中执行此操作,但它需要相当多的编码。


使用更现代的版本 (ruamel.yaml>0.14),您可以:

yaml = ruamel.yaml.YAML()
yaml.preserve_quotes = True

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

并保留单引号。

我假设你的yaml事先没有引号,有没有引号结果都是一样的

方法如下:

import yaml

myyaml = """
one: valueOne
two: valueTwo
three: valueThree
"""

# loading the yaml

yaml_dict = yaml.load(myyaml, yaml.FullLoader)
print(yaml_dict)

{'one': 'valueOne', 'two': 'valueTwo', 'three': 'valuethree'}

# Define the following class:

class SingleQuoted(str):
    pass

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

yaml.add_representer(SingleQuoted, single_quoted_presenter)

yaml_dict.update(three = SingleQuoted('valuethree'))

# Now dump as yaml

yaml.dump(yaml_dict, sys.stdout, sort_keys=False, default_flow_style=False)

# save it in a file by using an open file instead of stdout

,给出所需的yaml如下:

one: valueOne
two: valueTwo
three: 'valueThree'