我已经使用PyYAML
解析器几个月了,将文件类型转换为数据管道的一部分。我发现解析器有时非常特殊,似乎今天我偶然发现了另一个奇怪的行为。我目前正在转换的文件包含以下部分:
off:
yes: "Flavor text for yes"
no: "Flavor text for no"
我在字典中保留了当前嵌套的列表,以便我可以构建一个平面文档,但保存嵌套以便稍后转换回YAML。我得到TypeError
说我试图将str
和bool
类型连接在一起。我进行了调查,发现PyYaml
实际上正在上面我的部分文字并将其转换为以下内容:
with open(filename, "r") as f:
data = yaml.load(f.read())
print data
>> {False: {True: "Flavor text for yes", False: "Flavor text for no}}
我做了一个快速检查,发现PyYAML
正在为yes
,no
,true
,false
,on
执行此操作, off
。如果键不加引号,它只会执行此转换。引用的值和键将被正确传递。在寻找解决方案时,我发现此行为已记录here。
虽然知道引用密钥会阻止PyYAML
执行此操作可能会对其他人有所帮助,但我没有这个选项,因为我不是这些文件的作者并编写了我的代码尽可能少地触摸数据。
是否存在针对此问题的解决方法或覆盖PyYAML
中的默认转化行为的方法?
答案 0 :(得分:8)
PyYAML是YAML 1.1 conformant用于解析和发射,而对于YAML 1.1,这是at least partly documented行为,因此根本没有特质,但是有意识的设计。
在YAML 1.2中(在2009年取代了2005年的1.1规范),Off/On/Yes/No
的使用被删除,以及其他更改。
在ruamel.yaml
中(免责声明:我是该软件包的作者),round_trip_loader
是一个safe_loader,默认为YAML 1.2行为:
import ruamel.yaml as yaml
yaml_str = """\
off:
yes: "Flavor text for yes" # quotes around value dropped
no: "Flavor text for no"
"""
data = yaml.round_trip_load(yaml_str)
assert 'off' in data
print(yaml.round_trip_dump(data, indent=4))
给出了:
off:
yes: Flavor text for yes # quotes around value dropped
no: Flavor text for no
如果您的输出需要与1.1兼容,那么您可以转储
一个明确的version=(1, 1)
。
由于嵌套映射的标量值周围的引号是不必要的,因此在写出时会发出而不是。
如果您需要使用PyYAML执行此操作,请重写用于布尔识别的(全局)规则:
import yaml
from yaml.resolver import Resolver
import re
yaml_str = """\
off:
yes: "Flavor text for yes" # quotes around value dropped
no: "Flavor text for no"
"""
# remove resolver entries for On/Off/Yes/No
for ch in "OoYyNn":
if len(Resolver.yaml_implicit_resolvers[ch]) == 1:
del Resolver.yaml_implicit_resolvers[ch]
else:
Resolver.yaml_implicit_resolvers[ch] = [x for x in
Resolver.yaml_implicit_resolvers[ch] if x[0] != 'tag:yaml.org,2002:bool']
data = yaml.load(yaml_str)
print(data)
assert 'off' in data
print(yaml.dump(data))
给出了:
{'off': {'yes': 'Flavor text for yes', 'no': 'Flavor text for no'}}
off: {no: Flavor text for no, yes: Flavor text for yes}
这是有效的,因为PyYAML保留了一个全局字典(Resolver.yaml_implicit_resolvers
),它将第一个字母映射到(tag,re.match_pattern)值列表。对于o
,O
,y
和Y
,只有一个这样的模式(并且可以删除),但对于n
/ {{1您也可以匹配N
/ null
,因此您必须删除正确的模式。
删除后Null
,yes
,no
,on
不再被视为bool,但Off
和True
仍然是
答案 1 :(得分:3)
yaml.load
接受第二个参数,一个加载器类(默认为yaml.loader.Loader
)。预定义的加载器是许多其他加载器的混搭:
class Loader(Reader, Scanner, Parser, Composer, Constructor, Resolver):
def __init__(self, stream):
Reader.__init__(self, stream)
Scanner.__init__(self)
Parser.__init__(self)
Composer.__init__(self)
Constructor.__init__(self)
Resolver.__init__(self)
Constructor
类是执行数据类型转换的类。覆盖布尔转换的一种(kludgy,但快速)方法可能是:
from yaml.constructor import Constructor
def add_bool(self, node):
return self.construct_scalar(node)
Constructor.add_constructor(u'tag:yaml.org,2002:bool', add_bool)
它会覆盖构造函数用于将布尔标记数据转换为Python布尔值的函数。我们在这里做的只是逐字地返回字符串。
这会影响所有 YAML加载,因为您重写了默认构造函数的行为。更合适的方法是创建一个派生自Constructor
的新类,以及使用自定义构造函数的新Loader
对象。
答案 2 :(得分:1)
只需清理您的输入:
import yaml
def sanitize_load(s):
s = ' ' + s
for w in "yes no Yes No Off off On on".split():
s = s.replace(' ' + w + ':', ' "' + w + '":')
return yaml.load(s[1:])
with open(filename) as f:
data = sanitize_load(f.read())
print data
这比在pyyaml的可怕深处盲目戳戳要好得多。这些软件包带有两个,几乎但不完全相同的资源,是维护的噩梦。
答案 3 :(得分:0)
在工作中遇到此问题,必须以“正确”的方式实施。这是我采取的步骤。注意,我使用的是SafeLoader,而不是常规的Loader。步骤将非常相似。
常规步骤是
SafeConstuctor
SafeLoader
的自定义SafeConstructor
yaml.load
的“加载”函数,并传入自定义SafeLoader
使用自定义SafeConstructor
MySafeConstructor.py
from yaml.constructor import SafeConstructor
# Create custom safe constructor class that inherits from SafeConstructor
class MySafeConstructor(SafeConstructor):
# Create new method handle boolean logic
def add_bool(self, node):
return self.construct_scalar(node)
# Inject the above boolean logic into the custom constuctor
MySafeConstructor.add_constructor('tag:yaml.org,2002:bool',
MySafeConstructor.add_bool)
Constructor
。我们实质上只是在“添加”到该列表。MySafeLoader.py
from yaml.reader import *
from yaml.scanner import *
from yaml.parser import *
from yaml.composer import *
from MySafeConstructor import *
from yaml.resolver import *
class MySafeLoader(Reader, Scanner, Parser, Composer, MySafeConstructor, Resolver):
def __init__(self, stream):
Reader.__init__(self, stream)
Scanner.__init__(self)
Parser.__init__(self)
Composer.__init__(self)
MySafeConstructor.__init__(self)
Resolver.__init__(self)
import
的自定义安全加载器main.py
或您要加载的任何位置(也可以在__init__()
中使用)main.py
# Mandatory imports
from yaml import load
from MySafeLoader import MySafeLoader
def main():
filepath_to_yaml = "/home/your/filepath/here.yml"
# Open the stream, load the yaml doc using the custom SafeLoader
file_stream: TextIO = open(filepath_to_yaml , 'r')
yaml_as_dict = load(file_stream, MySafeLoader)
file_stream.close()
# Print our result
print(yaml_as_dict)
现在,我们可以使用标准加载器或为所需的布尔逻辑修改的自定义加载器。如果您想要除字符串之外的其他值,则可以尝试覆盖bool_values
list in the MySafeConstructor class,因为这是一个包含翻译逻辑的全局列表。
constructor.py
bool_values = {
'yes': True,
'no': False,
'true': True,
'false': False,
'on': True,
'off': False,
}
注意:如果执行此操作,则不想覆盖布尔逻辑,只需覆盖此列表即可。