Как я могу контролировать, какую скалярную форму PyYAML использует для моих данных?

У меня есть объект с атрибутом короткой строки и атрибутом длинной многострочной строки. Я хочу написать короткую строку в виде скаляра 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, как обращаться с моими атрибутами? Я бы хотел повлиять как на порядок, так и на стиль.

Ответы на вопрос(4)

Решение Вопроса

На основеКакие-нибудь библиотеки yaml в Python, которые поддерживают вывод длинных строк в виде блочных литералов или свернутых блоков?

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
 jfs14 окт. 2017 г., 07:37
@JasonS: это хороший отдельный вопрос. Вы можете попробовать передать свой собственный класс Dumperyaml.dump с переопределением методов submit_scalar, present_dict.
 Jason S13 окт. 2017 г., 22:25
любой способ сделать это так, чтобы он не влиял на глобальное состояние yaml, но влиял на один отдельный вызовdump()?

Влюбиться вподход @ lbtЯ получил этот код:

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)

Это делает каждую многострочную строку литералом блока.

Я пытался избежать исправления обезьяны. Полный кредит @lbt и @ J.F.Sebastian.

 jfs26 нояб. 2015 г., 18:49
хороший подход, позволяющий избежать явной пометки входных строк. Вы могли бы использоватьis_multiline = lambda s: len(s.splitlines()) > 1 это распознает новые строки Unicode автоматически, и это не возвращает true для единственной строки.
 xenosoz01 дек. 2015 г., 00:22
@ J.F.Sebastian Приятно видеть этот хороший трюк. Теперь код выглядит намного лучше. Большое спасибо!
 Jason S13 окт. 2017 г., 22:41
хм,style='|' кажется, не влияет на pyyaml
 oulenz16 янв. 2018 г., 11:26
@jfs Обратите внимание, чтоsplitlines игнорирует последние символы новой строки, поэтому, если вы хотите, чтобы однострочные строки с последней новой строкой обрабатывались с| стиль, вы должны проверить это отдельно. Или если\n это единственный тип перевода строки в вашем наборе данных, просто используйтеlen(data.split('\n')) > 1
 jfs16 янв. 2018 г., 11:51
@oulenz: «многострочный» означает «более одной» строки..split('\n') здесь не так.'\n' являетсяодин линия, а не две.

Я хотел любой вклад с\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

Вы можете использовать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

(включая комментарий, начинающийся в том же столбце, что и раньше)

Вы также можете создать этот вывод, начиная с нуля, но затем вам необходимо предоставить дополнительную информацию, например, явные позиции о том, где сложить.

Ваш ответ на вопрос