我希望能够在yaml中定义一个模式,使用pyyaml
读取它,然后使用voluptuous
(或其他模式验证器!)进行验证。然而,正如问题标题中所述,我遇到了需要为voluptuous实例化内置类str
,而不是它的字符串表示。
from voluptuous import Schema
import yaml
y = '''
a: str
b: int
c:
d: float
e: str
'''
yaml_schema = yaml.load(y,
Loader=yaml.CLoader)
schema1 = Schema(yaml_schema, required=True)
但是,这个模式现在正在寻找字符串str
作为a
的唯一可接受的值。使用直接pyyaml(例如:'a': !!python/int)失败。相反,我想要下面的模式:
schema2 = Schema({'a': str,
'b': int,
'c': {'d': float,
'e': str}},
required=True)
我很清楚eval
不是生产解决方案,但是下面的evaler
函数将把schema1
转换为schema2
…
def evaler(d):
out = {}
for k, v in d.items():
if isinstance(v, dict):
out[k] = evaler(v)
else:
out[k] = eval(v)
return out
## Tests:
## passing
v.Schema(evaler(yaml_schema),
required=True)({'a': 'foo',
'b': 2,
'c': {'d': 2.0,
'e': 'bar'}})
## failling
v.Schema(evaler(yaml_schema),
required=True)({'a': 3,
'b': 2,
'c': {'d': 2.0,
'e': 1}})
我也知道你可以实例化一个空类:
class foo: pass
globals()['foo']
但是对于内置的,这是不可能的:
globals()['int']
# KeyError: 'int'
我探索了new
和type
模块,但没有任何运气…
最安全、最简单、最清晰的解决方案是显式列出您关心的类型的映射:
TYPES = {
'str': str,
'int': int,
...
}
您可以通过从类型列表创建这个字典来消除重复(以牺牲一些灵活性为代价):
TYPES = {cls.__name__: cls for cls in [str, int, ...]}
然后可以递归遍历文档(就像在evaler
中所做的那样),并用TYPES[s]
替换每个字符串。如果您坚持按名称支持所有内置类型,而不单独列出它们,则可以使用builtins
模块(在Python 2中称为__builtin__
)。getattr
是你的朋友。你可能应该检查它是否是一个类型——有很多内建的名字不是。
无论如何都需要遍历文档。从PyYAML的角度来看,用作映射值的字符串"str"与用作映射键的字符串"a"具有相同的标记,因此您不能通过为该标记指定不同的类来做任何事情。虽然您可能会深入到它的内部,并引入一个以不同方式处理标量映射值的hack,但这只是一个hack。还有大量的额外工作要做。不值得。