运行时编译参数在 python 中的函数
Runtime compiling a function with arguments in python
我正在尝试使用 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
有没有办法用这种方式编译一个接受参数的函数,或者有没有其他方法可以做到这一点?
编译returns 代码对象以创建模块。在 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
。
此代码意味着您的函数运行模块的创建,而不是 return运行模块或函数本身。所以本质上,你实际做的等同于以下内容:
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代码(除了编译为字节码外默认没有静态编译),你可以传入自定义globals
和 locals
字典,然后从中提取值。
glob, loc = {}, {}
exec(code, glob, loc)
>>> loc['add'](4, 2)
6
但真正的答案是,如果您想这样做,最简单的方法通常是生成 Abstract Syntax Trees using the ast module,然后将其编译成模块代码并评估或执行模块。
如果您想进行字节码转换,我建议您查看 PyPi 上的 codetransformer 包。
TL;DR 使用 compile
只会为模块编写 return 代码,最重要的代码生成是使用 AST 或通过操作字节码。
is there any other way to do that?
值得一提的是:我最近创建了一个 @compile_fun
好东西,它大大简化了在函数上应用 compile
的过程。它依赖于 compile
因此与上述答案所解释的没有什么不同,但它提供了一种更简单的方法。您的示例写道:
@compile_fun
def add(a, b):
return a + b
assert add(1, 2) == 3
您可以看到您现在无法使用 IDE 调试到 add
。请注意,这不会提高运行时性能,也不会保护您的代码免受逆向工程,但如果您不希望用户在调试时看到函数的内部结构,这可能会很方便。请注意,明显的缺点是他们将无法帮助您调试您的库,因此请谨慎使用!
详情见makefun
documentation。
我正在尝试使用 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
有没有办法用这种方式编译一个接受参数的函数,或者有没有其他方法可以做到这一点?
编译returns 代码对象以创建模块。在 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
。
此代码意味着您的函数运行模块的创建,而不是 return运行模块或函数本身。所以本质上,你实际做的等同于以下内容:
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代码(除了编译为字节码外默认没有静态编译),你可以传入自定义globals
和 locals
字典,然后从中提取值。
glob, loc = {}, {}
exec(code, glob, loc)
>>> loc['add'](4, 2)
6
但真正的答案是,如果您想这样做,最简单的方法通常是生成 Abstract Syntax Trees using the ast module,然后将其编译成模块代码并评估或执行模块。
如果您想进行字节码转换,我建议您查看 PyPi 上的 codetransformer 包。
TL;DR 使用 compile
只会为模块编写 return 代码,最重要的代码生成是使用 AST 或通过操作字节码。
is there any other way to do that?
值得一提的是:我最近创建了一个 @compile_fun
好东西,它大大简化了在函数上应用 compile
的过程。它依赖于 compile
因此与上述答案所解释的没有什么不同,但它提供了一种更简单的方法。您的示例写道:
@compile_fun
def add(a, b):
return a + b
assert add(1, 2) == 3
您可以看到您现在无法使用 IDE 调试到 add
。请注意,这不会提高运行时性能,也不会保护您的代码免受逆向工程,但如果您不希望用户在调试时看到函数的内部结构,这可能会很方便。请注意,明显的缺点是他们将无法帮助您调试您的库,因此请谨慎使用!
详情见makefun
documentation。