首页 > 解决方案 > 不使用字典动态创建变量

问题描述

用例 - 我正在使用在另一个系统中创建的 python 代码,并将其分解为单独的函数并将它们连接在一起。这项工作的全部目的是将由于许多业务原因我们没有编写的大型 Python 函数分解成更小的 Python 函数。

我可以获取代码,解析变量,并在执行此操作时任意将它们放入 dict 中,但这不仅仅是一点点工作,我想在此之前将其运行到地面。

我知道我们几乎不 应该这样做,但我需要这样做,因为我是为我没有编写的函数生成包装器的代码,我需要在函数内部动态创建变量。我也不能使用exec,因为该值可能是一个复杂的结构(例如,一个字典)。

因此,我们所做的重点是要求原始作者不对传入的代码进行任何更改,同时仍然跨多个独立实体执行它。

就像这里列出的示例一样 - 我们在第一个出口(理想情况下是函数、lambdas 和所有变量)中捕获尽可能多的状态,并在第二个函数中重新设置它们,以便以前具有相同的两个函数范围和上下文可以在没有更改的情况下执行。

这是一个可重现的代码块(所有与此无关b的代码都是我可以用来包装赋值的代码:

原来的:

def original_function():
    b = 100
    b = b + 20

生成的函数:

def fun_1() -> str:
    import dill
    from base64 import urlsafe_b64decode, urlsafe_b64encode
    from types import ModuleType

    b = 100

    locals_keys = frozenset(locals().keys())
    global_keys = frozenset(globals().keys())
    __context_export = {}
    for val in locals_keys:
        if not val.startswith("_") and not isinstance(val, ModuleType):
            __context_export[val] = dill.dumps(locals()[val])

    for val in global_keys:
        if not val.startswith("_") and not isinstance(val, ModuleType):
            __context_export[val] = dill.dumps(globals()[val])

    b64_string = str(urlsafe_b64encode(dill.dumps(__context_export)), encoding="ascii")

    from collections import namedtuple

    output = namedtuple("FuncOutput", ["context"])
    return output(b64_string)


def fun_2(context):
    import dill
    from base64 import urlsafe_b64encode, urlsafe_b64decode
    from types import ModuleType

    __base64_decode = urlsafe_b64decode(context)
    __context_import_dict = dill.loads(__base64_decode)
    for k in __context_import_dict:
        val = dill.loads(__context_import_dict[k])
        if globals().get(k) is None and not isinstance(val, ModuleType):
            globals()[k] = val

    b = b + 20


output = fun_1()
fun_2(output[0])

运行此程序时出现的错误是:

UnboundLocalError: local variable 'b' referenced before assignment

谢谢大家的帮助!

标签: pythonpython-3.x

解决方案


好的,在我理解了这些问题之后,这很容易解决。老实说,这更有意义 - 因为我是从外部获取代码(作为字符串),所以我应该挂载必要的全局变量并在封闭环境中执行是有道理的。

明确 - 这是在用户环境中执行的,因此安全性不是问题。但这有效!

def fun_1() -> str:
    import dill
    from base64 import urlsafe_b64decode, urlsafe_b64encode
    from types import ModuleType, FunctionType

    # CODE FROM EXTERNAL

    b = 100

    # END CODE

    locals_keys = frozenset(locals().keys())
    global_keys = frozenset(globals().keys())
    __context_export = {}
    for val in locals_keys:
        if (
            not val.startswith("_")
            and not isinstance(val, ModuleType)
            and not isinstance(val, FunctionType)
        ):
            __context_export[val] = dill.dumps(locals()[val])

    for val in global_keys:
        if (
            not val.startswith("_")
            and not isinstance(val, ModuleType)
            and not isinstance(val, FunctionType)
        ):
            __context_export[val] = dill.dumps(globals()[val])

    b64_string = str(urlsafe_b64encode(dill.dumps(__context_export)), encoding="ascii")

    from collections import namedtuple

    output = namedtuple("FuncOutput", ["context"])
    return output(b64_string)


def fun_2(context):
    import dill
    from base64 import urlsafe_b64encode, urlsafe_b64decode
    from types import ModuleType, FunctionType
    from pprint import pprint as pp

    __base64_decode = urlsafe_b64decode(context)
    __context_import_dict = dill.loads(__base64_decode)

    variables = {}
    for k in __context_import_dict:
        variables[k] = dill.loads(__context_import_dict[k])

    loc = {}

    # CODE FROM EXTERNAL

    inner_code_to_execute "b = b + 20"

    # END CODE 

    exec(inner_code_to_execute, variables, loc)
    print(loc["b"])


output = fun_1()
fun_2(output[0])

推荐阅读