【问题标题】:Python: YAML dictionary of functions: how to load without converting to stringsPython:YAML 函数字典:如何在不转换为字符串的情况下加载
【发布时间】:2017-06-21 08:19:12
【问题描述】:

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

"COLUMN_NAME": column_function

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

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

'COLUMN_NAME': 'column_function' 

现在我无法按预期使用它 - 'column_function' 不指向 column_function

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

我认为this thread 与我的问题有关,但我不确定解决该问题的最佳方法。

  • 我认为getattrsetattr 已经出局,因为它们对实例化对象进行操作,而我有一个简单的脚本。
  • 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. ")

【问题讨论】:

    标签: python string python-3.x dictionary yaml


    【解决方案1】:

    要查找名称 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()
    

    这改编自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
    

    假设子目录my 有一个文件test.py 的内容:

    import math
    def pi():
        return math.pi
    

    你可以使用:

    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)
    

    得到:

    COLUMN_NAME: 3                       # tagged
    PI_VAL: 3.141592653589793            # also tagged
    ANSWER: forty-two                    # this one has no tag
    

    如果您不关心将 data 转储为保留 cmets 的 YAML,则可以使用 SafeLoadersafe_load() 而不是 RoundTripLoader resp。 round_trip_loader().

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-02-09
      • 1970-01-01
      • 1970-01-01
      • 2013-09-24
      • 2017-02-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多