在Python中编译带参数的函数的运行时

f3temu5u  于 2022-12-15  发布在  Python
关注(0)|答案(3)|浏览(260)

我尝试使用compile to runtime生成一个接受参数的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
有没有办法用这种方法编译一个接受参数的函数,或者有没有其他方法?

2jcobegt

2jcobegt1#

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(f_code, {}, "add")

>>> add(4, 2)
6

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

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

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

但真实的的答案是,如果您想这样做,最简单的方法通常是使用ast module生成Abstract Syntax Trees,并将其编译为模块代码,然后计算或执行该模块。
如果你想做字节码转换,我建议看看PyPi上的codetransformer包。

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

j0pj023g

j0pj023g2#

有没有其他办法?
值得的是:我最近创建了一个@compile_fun goodie,它极大地简化了在函数上应用compile的过程。它依赖于compile,所以与上面的答案解释的没有什么不同,但它提供了一种更简单的方法。您的示例如下:

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

assert add(1, 2) == 3

您可以看到,现在无法使用IDE调试add。请注意,这不会提高运行时性能,也不会保护代码免受逆向工程的影响,但如果您不希望用户在调试时看到函数的内部信息,这可能会很方便。请注意,明显的缺点是他们无法帮助您调试库,因此请小心使用!
有关详细信息,请参见makefun文档。

hyrbngr7

hyrbngr73#

我觉得这样能更好地实现你的愿望

import types

text = "lambda (a, b): return a + b"
code = compile(text, '<string>', 'eval')
body = types.FunctionType(code, {})
fn = body()

print(fn(4, 2))

匿名函数解决了隐式命名空间的问题,并且通过使用模式 'eval' 将其作为值返回比将其从代码内容中移除更干净,因为它不依赖于编译器的特定习惯。
更有用的是,正如您似乎已经注意到但还没有开始使用的那样,由于您导入了ast,传递给compiletext实际上可以是一个ast对象,因此您可以对它使用ast转换。

import types
import ast
from somewhere import TransformTree

text = "lambda (a, b): return a + b"
tree = ast.parse(text)
tree = TransformTree().visit(tree)
code = compile(text, '<string>', 'eval')
body = types.FunctionType(code, {})
fn = body()

print(fn(4, 2))

相关问题