Python 如何在YAML中引用现有变量?
假设我的Python脚本中已经定义了一个对象,它充当一些随机项的容器。容器的每个属性对应于一个项。在这个简单的例子中,我有一个Python 如何在YAML中引用现有变量?,python,yaml,Python,Yaml,假设我的Python脚本中已经定义了一个对象,它充当一些随机项的容器。容器的每个属性对应于一个项。在这个简单的例子中,我有一个ITEMS对象,它有一个BALL属性,该属性指向一个BALL实例 现在,我需要在YAML中加载一些内容,但我希望这些内容能够引用已经定义的现有项变量。这可能吗?也许是类似于 ITEMS = Items() setattr(Items, 'BALL', Ball()) yaml_text = "item1: !!python/object:ITEMS.BALL" yaml
ITEMS
对象,它有一个BALL
属性,该属性指向一个BALL
实例
现在,我需要在YAML中加载一些内容,但我希望这些内容能够引用已经定义的现有项变量。这可能吗?也许是类似于
ITEMS = Items()
setattr(Items, 'BALL', Ball())
yaml_text = "item1: !!python/object:ITEMS.BALL"
yaml_items = yaml.load(yaml_text)
加载YAML后,我的目标是使YAML_items['item1']
成为items
对象中的Ball
实例。@martineau引用了文档:
[…]提供特定于Python的标记,允许表示任意Python对象
表示,而不是构造。这意味着您可以将任何Python对象转储到YAML,但不能引用YAML中现有的Python对象
也就是说,您当然可以添加自己的构造函数:
import yaml
def eval_constructor(loader, node):
return eval(loader.construct_scalar(node))
yaml.add_constructor(u'!eval', eval_constructor)
some_value = '123'
yaml_text = "item1: !eval some_value"
yaml_items = yaml.load(yaml_text)
注意eval
ing配置数据的安全影响。将任意Python代码写入YAML文件即可执行强>
主要是从复制过来的。这里有一种方法,它使用了在“到另一个问题”中定义的di()
函数。它接受从内置的id()
函数返回的整数值,并将其转换为字符串。yaml.load()
函数将调用一个自定义构造函数,该构造函数随后执行与该过程相反的操作,以确定返回的对象
警告:这充分利用了一个事实,即至少在使用CPython时,id()
函数返回内存中Python对象的地址,因此它可能无法与解释器的其他实现一起工作
import _ctypes
import yaml
def di(obj_id):
""" Reverse of id() function. """
return _ctypes.PyObj_FromPtr(obj_id)
def py_object_constructor(loader, node):
return di(int(node.value))
yaml.add_constructor(u'!py_object', py_object_constructor)
class Items(object): pass
def Ball(): return 42
ITEMS = Items()
setattr(Items, 'BALL', Ball()) # Set attribute to result of calling Ball().
yaml_text = "item1: !py_object " + str(id(ITEMS.BALL))
yaml_items = yaml.load(yaml_text)
print(yaml_items['item1']) # -> 42
如果您同意使用eval()
,您可以将其形式化,并通过对yaml
模块的load()
函数进行猴子补丁来对yaml流进行一些预处理,从而使其更易于使用:
import _ctypes
import re
import yaml
#### Monkey-patch yaml module.
def _my_load(yaml_text, *args, **kwargs):
REGEX = r'@@(.+)@@'
match = re.search(REGEX, yaml_text)
if match:
obj = eval(match.group(1))
yaml_text = re.sub(REGEX, str(id(obj)), yaml_text)
return _yaml_load(yaml_text, *args, **kwargs)
_yaml_load = yaml.load # Save original function.
yaml.load = _my_load # Change it to custom version.
#### End monkey-patch yaml module.
def di(obj_id):
""" Reverse of id() function. """
return _ctypes.PyObj_FromPtr(obj_id)
def py_object_constructor(loader, node):
return di(int(node.value))
yaml.add_constructor(u'!py_object', py_object_constructor)
class Items(object): pass
def Ball(): return 42
ITEMS = Items()
setattr(Items, 'BALL', Ball()) # Set attribute to result of calling Ball().
yaml_text = "item1: !py_object @@ITEMS.BALL@@"
yaml_items = yaml.load(yaml_text)
print(yaml_items['item1']) # -> 42
您使用的第三方YAML库/模块是什么?“除非这是任何东西的一个特性,否则你不可能轻松做到这一点。@martineau我已经用简单的东西尝试了PyYAML和ruamel.yaml,但我们使用的模块仍然是待定的。我只是在看PyYAML模块的功能,它说PyYAML具有完整的YAML 1.1解析器、Unicode支持、pickle支持、功能强大的扩展API和合理的错误消息。PyYAML支持标准的YAML标记,并提供特定于Python的标记,允许表示任意Python对象。”(强调我的),所以听起来你只是需要弄清楚如何做最后一部分。有没有文档,你读过吗?@martineau我在文档中查找了和。有一些方法可以使用python特定的标记,就像你提到的,但我没有看到任何使用我最初提出的现有对象的方法。定义我自己的构造ctor是我考虑过的一种策略,但我希望避免这种情况,这样我就不必自己进行任何文本解析或eval
ing。尽管如此,我还是很感激这个建议。谢谢!这是一个非常有趣和聪明的实现,我可能会尝试一下。谢谢!还要注意,这不使用eva()l
,因此比@flyx的答案更安全。对JSON执行类似的操作更好、更容易实现,因为JSON
模块有一个钩子,允许类似+str(id(ITEMS.BALL))的内容
部分将更自动地完成。我不太熟悉yaml
模块,安装它只是为了测试我的答案,因此可能有比此处所示更好的实现方法。