Python:YAML 函数字典:如何加载而不转换为字符串

Zub*_*ubo 5 python string dictionary yaml python-3.x

我有一个 YAML 配置文件,其中包含一个字典,如下所示:

"COLUMN_NAME": column_function
Run Code Online (Sandbox Code Playgroud)

它将字符串映射到函数(存在并且应该被调用)。

但是,当我使用 加载它时yaml,我看到加载的字典现在将字符串映射到字符串:

'COLUMN_NAME': 'column_function' 
Run Code Online (Sandbox Code Playgroud)

现在我不能按预期使用它 -'column_function'不指向column_function.

加载 mydict以便映射到我的函数的好方法是什么?在对这个问题进行了一些搜索和阅读之后,我对使用eval或类似的东西非常谨慎,因为配置文件是用户编辑的。

我认为这个线程是关于我的问题,但我不确定解决它的最佳方法。

  • getattr并且setattr我认为是因为它们对实例化的对象进行操作,而我有一个简单的脚本。
  • globals()vars()locals()为我提供dicts 个变量,我想我应该globals()根据this 使用

我应该为我的配置中的每个键值对查找其中的字符串dict吗?这是一个好方法吗:

for (key, val) in STRING_DICTIONARY.items():
    try: 
        STRING_DICTIONARY[key] = globals()[val]   
    except KeyError:
        print("The config file specifies a function \"" + val 
               + "\" for column \"" + key 
               + "\". No such function is defined, however. ")
Run Code Online (Sandbox Code Playgroud)

Ant*_*hon 2

要查找名称val并以通用方式对其进行评估,我将使用以下命令:

def fun_call_by_name(val):
    if '.' in val:
        module_name, fun_name = val.rsplit('.', 1)
        # you should restrict which modules may be loaded here
        assert module_name.startswith('my.')
    else:
        module_name = '__main__'
        fun_name = val
    try:
        __import__(module_name)
    except ImportError as exc:
        raise ConstructorError(
            "while constructing a Python object", mark,
            "cannot find module %r (%s)" % (utf8(module_name), exc), mark)
    module = sys.modules[module_name]
    fun = getattr(module, fun_name)
    return fun()
Run Code Online (Sandbox Code Playgroud)

这是改编自 的ruamel.yaml.constructor.py:find_python_name(),用于从字符串标量创建对象。如果val提交的 包含一个点,它将假设您正在另一个模块中查找函数名称。

但我不会神奇地解释顶级字典中的值。YAML 具有标记机制(对于特定标记,该find_python_name()方法将起作用,以控制创建的实例的类型)。
如果您可以控制 YAML 文件的外观,请使用标签有选择地不创建字符串,如下文件所示input.yaml

COLUMN_NAME: !fun column_function    # tagged
PI_VAL: !fun my.test.pi              # also tagged
ANSWER: forty-two                    # this one has no tag
Run Code Online (Sandbox Code Playgroud)

假设一个子目录my包含一个包含以下内容的文件test.py

import math
def pi():
    return math.pi
Run Code Online (Sandbox Code Playgroud)

您可以使用:

import sys
import ruamel.yaml

def column_function():
    return 3

def fun_constructor(loader, node):
    val = loader.construct_scalar(node)
    return fun_call_by_name(val)

# add the constructor for the tag !fun
ruamel.yaml.add_constructor('!fun', fun_constructor, Loader=ruamel.yaml.RoundTripLoader)

with open('input.yaml') as fp:
    data = ruamel.yaml.round_trip_load(fp)
assert data['COLUMN_NAME'] == 3
ruamel.yaml.round_trip_dump(data, sys.stdout)
Run Code Online (Sandbox Code Playgroud)

要得到:

COLUMN_NAME: 3                       # tagged
PI_VAL: 3.141592653589793            # also tagged
ANSWER: forty-two                    # this one has no tag
Run Code Online (Sandbox Code Playgroud)

如果您不关心转储data为 YAML 并保留注释,则可以使用SafeLoaderandsafe_load()代替RoundTripLoaderresp。round_trip_loader()