11

过去,我some_fancy_printing_loggin_func(yaml.dump(...), ...)使用 ruamel.yaml 的向后兼容部分做了类似的事情,但我想将我的代码转换为使用最新的 API,以便我可以利用一些新的格式设置。

但是,我讨厌我必须指定一个流来ruamel.yaml.YAML.dump()......我不希望它直接写入流;我只是想让它把输出返回给调用者。我错过了什么?

PS:我知道我可以做类似以下的事情,虽然我当然试图避免它。

f = io.StringIO()
yml.dump(myobj, f)
f.seek(0)
my_logging_func(f.read())
4

3 回答 3

10

在如此频繁地需要此功能后,我将这个答案(一个小包装器ruamel.yaml)放入此处的 pip 模块中。

TLDR

pip install ez_yaml

import ez_yaml

ez_yaml.to_string(obj=your_object    , options={})

ez_yaml.to_object(file_path=your_path, options={})
ez_yaml.to_object(string=your_string , options={})

ez_yaml.to_file(your_object, file_path=your_path)

原始问题的Hacky /复制粘贴解决方案

def object_to_yaml_str(obj, options=None):
    # 
    # setup yaml part (customize this, probably move it outside this def)
    # 
    import ruamel.yaml
    yaml = ruamel.yaml.YAML()
    yaml.version = (1, 2)
    yaml.indent(mapping=3, sequence=2, offset=0)
    yaml.allow_duplicate_keys = True
    # show null
    def my_represent_none(self, data):
        return self.represent_scalar(u'tag:yaml.org,2002:null', u'null')
    yaml.representer.add_representer(type(None), my_represent_none)
    
    # 
    # the to-string part
    # 
    if options == None: options = {}
    from io import StringIO
    string_stream = StringIO()
    yaml.dump(obj, string_stream, **options)
    output_str = string_stream.getvalue()
    string_stream.close()
    return output_str

原始答案(如果您想更多地自定义配置/选项)

import ruamel.yaml
from io import StringIO
from pathlib import Path

# setup loader (basically options)
yaml = ruamel.yaml.YAML()
yaml.version = (1, 2)
yaml.indent(mapping=3, sequence=2, offset=0)
yaml.allow_duplicate_keys = True
yaml.explicit_start = False
# show null
def my_represent_none(self, data):
    return self.represent_scalar(u'tag:yaml.org,2002:null', u'null')
yaml.representer.add_representer(type(None), my_represent_none)

# o->s
def object_to_yaml_str(obj, options=None):
    if options == None: options = {}
    string_stream = StringIO()
    yaml.dump(obj, string_stream, **options)
    output_str = string_stream.getvalue()
    string_stream.close()
    return output_str

# s->o
def yaml_string_to_object(string, options=None):
    if options == None: options = {}
    return yaml.load(string, **options)

# f->o
def yaml_file_to_object(file_path, options=None):
    if options == None: options = {}
    as_path_object = Path(file_path)
    return yaml.load(as_path_object, **options)

# o->f
def object_to_yaml_file(obj, file_path, options=None):
    if options == None: options = {}
    as_path_object = Path(Path(file_path))
    with as_path_object.open('w') as output_file:
        return yaml.dump(obj, output_file, **options)

# 
# string examples
# 
yaml_string = object_to_yaml_str({ (1,2): "hi" })
print("yaml string:", yaml_string)
obj = yaml_string_to_object(yaml_string)
print("obj from string:", obj)

# 
# file examples
# 
obj = yaml_file_to_object("./thingy.yaml")
print("obj from file:", obj)
object_to_yaml_file(obj, file_path="./thingy2.yaml")
print("saved that to a file")

咆哮

我感谢 Mike Night 解决了原来的“我只是希望它把输出返回给调用者”,并指出 Anthon 的帖子未能回答这个问题。我将进一步做:Anthon 你的模块很棒;往返令人印象深刻,是有史以来为数不多的旅行之一。但是,(这在 Stack Overflow 上经常发生)作者的工作不是让其他人的代码运行时高效。明确的权衡很好,作者应该帮助人们理解他们选择的后果。添加警告,包括名称中的“慢”等可能非常有帮助。但是,ruamel.yaml 文档中的方法;创建一个完整的继承类,不是“显式”的。它们是阻碍和混淆的,

许多用户理所当然地不关心运行时性能。我的程序的运行时间(没有 YAML)是 2 周。一个 500,000 行的 yaml 文件在几秒钟内被读取。2 周和几秒钟都与项目无关,因为它们是 CPU 时间,项目纯粹按工时计费。

由于正在对其执行其他操作,YAML 代码已经是一个字符串对象。将其强制转换为流实际上会导致更多开销。消除对 YAML 字符串形式的需求将涉及重写几个主要库并可能需要数月的努力;在这种情况下,使流成为非常不切实际的选择。

假设将其保留为流甚至是可能的,并且该项目按 CPU 时间而不是工时计费;优化 500,000 行的 yaml 文件作为字符串将提高 ≤0.0001% 的效率。花在找出这个问题的答案上的额外时间,以及其他人理解解决方法所花的时间,本可以花在提高其中一个每秒被调用 100 次的 c 函数的效率上。因此,即使我们确实关心 CPU 时间,特定方法仍然不是一个有用的选择。

忽略该问题同时还建议用户花费大量时间重写其应用程序的帖子不是答案。尊重他人,假设他们通常知道自己在做什么并且知道替代方案。然后,提供可能更有效的方法将得到赞赏而不是拒绝。

[结束吐槽]

于 2020-07-30T19:18:32.677 回答
6

总有一种情况需要一些意想不到的东西(即使这与通常情况下的最佳实践相矛盾)。这是一个例子:

在这种情况下,我需要 yaml 作为字符串。不,使用文件而不是字符串不会在这里剪切它,因为我将多次创建此 input_yaml,因为我需要多次执行此 pypandoc 转换。创建单个文件会更加混乱!

output = pypandoc.convert_text(input_yaml, to='markdown_strict', format='md', filters=filters)

input_yaml = """
---
bibliography: testing.bib
citation-style: ieee-with-url.csl
nocite: |

 @*
...
"""

正因为如此,我不得不回到 PyYAML。它允许我

yaml_args = {'bibliography':'testing.bib', 'citation-style':'ieee-with-url.csl'}

test = yaml.dump(yaml_args, default_flow_style=False)
test = "---\n"+ test + "nocite: | \n\n @* \n...\n"
output = pypandoc.convert_text(test, to='markdown_strict', format='md', filters=filters)

笨拙,但在这种情况下我能找到最好的。

于 2019-07-08T08:59:51.253 回答
2

我不确定你是否真的遗漏了一些东西,如果你正在使用流,你应该——最好——继续使用流。然而,许多 ruamel.yaml 和 PyYAML 的用户似乎都忽略了这一点,因此他们这样做了:

print(dump(data))

代替

dump(data, sys.stdout)

前者可能适用于(PyYAML)文档中使用的非真实数据,但它会导致对真实数据的不良习惯。

最好的解决方案是让你的my_logging_func()流导向。这可以例如如下完成:

import sys
import ruamel.yaml

data = dict(user='rsaw', question=47614862)

class MyLogger:
    def write(self, s):
        sys.stdout.write(s.decode('utf-8'))

my_logging_func = MyLogger()
yml = ruamel.yaml.YAML()
yml.dump(data, my_logging_func)

这使:

user: rsaw
question: 47614862

但请注意,它MyLogger.write()会被多次调用(在本例中为 8 次),如果您需要一次处理一条线路,则必须进行线路缓冲。

尽管您可以包装非流式接口,但这并不总是很方便。ruamel.yaml<=0.15.34你可以滥用这个transform论点。此参数需要一个函数,该函数以 YAML 文档的完整字符串表示形式传递(这与旧 API 返回字符串一样低效,所以要小心),并且应该返回一个转换后的字符串,然后将其流式传输。如果将流设为接收器,则不必关心函数的返回值:

import sys
import ruamel.yaml

data = dict(user='rsaw', question=47614862)

def my_logging_func(s):
    print(s, end='')

class NullStream:
    def write(self, s):
        pass

yml = ruamel.yaml.YAML()
yml.dump(data, NullStream(), transform=my_logging_func)

获得相同的输出。

使用ruamel.yaml>0.15.34,假设您提供transform参数,您可以None作为流提供:

import sys
import ruamel.yaml

data = dict(user='rsaw', question=47614862)

def my_logging_func(s):
    print(s, end='')

yml = ruamel.yaml.YAML()
yml.dump(data, None, transform=my_logging_func)

你甚至可以滥用更多的东西来做:

import ruamel.yaml

data = dict(user='rsaw', question=47614862)

yml = ruamel.yaml.YAML()
yml.dump(data, None, transform=print)

(但它会在输出末尾为您提供额外的换行符,就像旧的一样print(dump(data)))。

于 2017-12-03T10:09:03.657 回答