将输入字符串转换为可调用函数并将其存储为变量的正确方法?

时间:2019-01-31 05:09:42

标签: python

我正在测试一些代码来回答Possible to turn an input string into a callable function object in Python?,并且该问题被重复了,但我在源答案中找不到该问题的答案。

SO上有许多与此类似的答案,但是都没有解决如何从function返回string的问题。 exec()不返回function.,它仅执行任意代码。

我的问题是,将字符串转换为函数并作为函数对象返回的正确方法是吗?

my_string = "def add5(x):return x + 5"

def string_to_func(my_string):
    exec(my_string)
    for var_name, var in locals().items():
        if callable(var):
            return var
    print("no callable in string")
    #fallback code.

add5 = string_to_func(my_string)
print(add5(3))

PS :如果原始问题重新打开,我将删除此问题。

1 个答案:

答案 0 :(得分:1)

粗略地说,如果您假设输入是完全受信任的,并且所提供的字符串具有有效的Python语法,则会产生一个可调用的函数,并且如果exec不能使用,可以提供类似的东西

import ast
import types

def string_to_function(source):
    tree = ast.parse(source)
    if len(tree.body) != 1 or not isinstance(tree.body[0], ast.FunctionDef):
        raise ValueError('provided code fragment is not a single function')
    co = compile(tree, 'custom.py', 'exec')
    # first constant should be the code object for the function
    return types.FunctionType(co.co_consts[0], {})

示例:

f = string_to_function("""
def my_function(x):
    return x + 5
"""
)

print('f = %d' % f(5))

输出

f = 10

该代码确保所提供的源具有单个函数定义,并假定生成的字节码的组织方式(即,仅适用于内置于当前版本的Python中的编译器,其中,生成的字节码用于放置代码对象为co_consts中第0个元素中的源中定义的单个函数)。此答案的previous version使用了exec(提问者“无论如何都不是exec的忠实拥护者”),其执行方式是将结果绑定到特定目标中,并且该方法应该更多可靠,因为它不涉及较低级别的结构,尽管可以使用此处使用的ast模块进行完整性检查,而不是该原始版本。

还请注意,此答案仅适用于Python 3 +。

进一步编辑:

实际上,我对使用exec来执行(固定输入)任意代码的用法感到有些m贬,仅仅是因为它实际上实际上经常被很多人误解了。 。在这种特殊情况下,如果验证仅接受特定的来源,则并不一定意味着每个语句都将立即执行。在这种情况下尤其如此(在我最初的懒惰答案中不能正确保证 ,但这是真正了解框架实际作用的地方,这对于进行涉及动态编译的任何事情都很重要。语言框架中的代码,并考虑到需要更高级别的“安全性”(事实相反之后立即执行功能,因此我最初并未实现),因此在编辑中使用了ast,现在客观上更好)。

因此,在给定单个函数定义的源输入的情况下,调用exec(co)到底具有什么作用呢?可以通过查看字节码来确定,如下所示:

>>> dis.dis(co)
  2           0 LOAD_CONST               0 (<code object my_function at 0x7fdbec44c420, file "custom.py", line 2>)
              2 LOAD_CONST               1 ('my_function')
              4 MAKE_FUNCTION            0
              6 STORE_NAME               0 (my_function)
              8 LOAD_CONST               2 (None)
             10 RETURN_VALUE

它要做的就是加载代码对象,使其成为一个适当的函数,并将结果分配给my_function(在当前相关的范围内),实际上就是这样。因此,是的,正确的方法是验证源绝对是此处的函数定义(因为通过检查AST进行的验证比仅包含一条语句的更幼稚的验证更为安全),然后在特定的{ {1}}并从那里提取任务。考虑到提供的任何功能都将立即执行,因此在这种情况下使用dict本质上不会(或更多)安全。