运行时在python中使用参数编译函数

时间:2018-07-19 18:39:22

标签: python-3.x codegen

我正在尝试使用compile在运行时生成一个接受参数的Python函数,如下所示。

import types
import ast

code = compile("def add(a, b): return a + b", '<string>', 'exec')
fn = types.FunctionType(code, {}, name="add")
print(fn(4, 2))

但是失败了

TypeError: <module>() takes 0 positional arguments but 2 were given

反正有没有使用这种方法来编译一个接受参数的函数,或者还有其他方法可以做到这一点?

2 个答案:

答案 0 :(得分:1)

Compile返回代码对象以创建模块。在Python 3.6中,如果要反汇编代码对象:

>>> import dis
>>> dis.dis(fn)
 0 LOAD_CONST    0 (<code object add at ...., file "<string>" ...>)
 2 LOAD_CONST    1 ('add')
 4 MAKE_FUNCTION 0
 6 STORE_NAME    0 (add)
 8 LOAD_CONST    2 (None)
10 RETURN_VALUE

从字面上翻译为make function; name it 'add'; return None

此代码表示您的函数运行模块的创建,而不返回模块或函数本身。因此,从本质上讲,您实际上在做什么等同于以下内容:

def f():
    def add(a, b):
        return a + b

print(f(4, 2))

关于您如何解决的问题,答案是取决于您要做什么。例如,如果您想使用compile来编译函数,那么简单的答案就是,如果不做以下类似的事情,您将无法做到。

# 'code' is the result of the call to compile.
# In this case we know it is the first constant (from dis),
# so we will go and extract it's value
f_code = code.co_consts[0]
add = FunctionType(code, {}, "add")

>>> add(4, 2)
6

由于在Python中定义函数需要运行Python代码(默认情况下,除了编译为字节码外,没有静态编译),因此您可以传入自定义globalslocals字典,然后提取这些值。

glob, loc = {}, {}
exec(code, glob, loc)

>>> loc['add'](4, 2)
6

但是真正的答案是,如果您想这样做,最简单的方法通常是使用Abstract Syntax Trees生成ast module,并将其编译为模块代码并评估或执行模块。

如果您想进行字节码转换,建议您查看PyPi上的codetransformer包。

使用compile

TL; DR 只会返回模块的代码,并且最严重的代码生成是通过AST或通过操纵字节码完成的。

答案 1 :(得分:0)

还有其他方法吗?

物有所值:我最近创建了一个@compile_fun好吃的东西,可以大大简化在函数上应用compile的过程。它依靠compile,因此与上述答案所解释的没什么不同,但是它提供了一种更简单的方法。您的示例写道:

@compile_fun
def add(a, b):
    return a + b

assert add(1, 2) == 3

您会看到现在无法使用IDE调试到add中。请注意,这不会提高运行时性能,也不会保护代码免受逆向工程的影响,但是如果您不希望用户在调试时看到函数的内部结构,这可能会很方便。请注意,明显的缺点是它们将无法帮助您调试lib,因此请谨慎使用!

有关详细信息,请参见makefun documentation