Python代码中定义函数的方式大致有下面几种,
通过def直接定义函数,
def foo():
return 'bar'
通过lambda定义匿名函数,
foo = lambda: 'bar'
在类上定义__call__
,让对象可以用函数调用方式访问,
class Foo(object):
def __call__(self):
return 'bar'
上述几种方式都是在代码中直接进行定义,有没有方法从字符串来动态构建函数?通常情况下这种需求就不那么合理,但终归在某些场景下这种方式会变成一种选择。比如需要在配置某种格式的“公式”,包含四则运算以及简单的数学函数调用。这种需求当然可以考虑自己实现简单的解析处理,不过若是能借用Python自身的能力,就会变得更为方便,且正确性能够得到保证。
格式约定如下,
{energy} = {energy} * 1.2 + sqrt(2)
变量包裹在{}
当中,公式中可以存在四则运算与函数调用,如何将这个公式字符串转化为Python函数,在不手动解析的情况下想到了下面两种方式,
根据公式字符串,生成独立的Python文件,然后在代码中import
# generated.py
from math import *
def func(obj):
obj.enerty = obj.energy * 1.2 + sqrt(2)
# main.py
import generated
generated.func(...)
基本的变量解析还是需要的,公式中的变量包裹在{}
便于解析。在程序启动阶段将所有定义的公式写入一个文件,在其余地方import。这种方式对运行时性能几无影响,只是在预处理阶段需要多做些工作。
动态编译代码,直接创建函数
还是以上面的代码片段为例,创建生成字符串func_def
,
def func(obj):
obj.enerty = obj.energy * 1.2 + sqrt(2)
调用complete
进行编译,再通过types.FunctionType构建函数,
module_code = compile(func_def, '<>', 'exec')
function_code = [code for code in module_code.co_consts if isinstance(code, types.CodeType)][0]
environment = {
'sqrt': math.sqrt,
}
func = types.FunctionType(function_code, environment)
上述逻辑执行完成之后func即为最终可调用函数。 简单说明下上述代码逻辑,
compile为内置函数,用于编译。第二个参数表明来源,这里是字符串所以传入'<>',第三个参数为模式,允许传入'eval'/'exec'/'single',这里函数定义不止单一语句,因此需要选择'exec'。
compile编译之后会生成code对象,code对象中的各个属性在Python code objects文档中进行了描述。code.co_consts记录了其中bytecode使用到的对象。上面用于编译的代码只包含一个函数定义,因此直接获取其中类型为types.CodeType的对象。
types.FunctionType也是内置的函数,通过help可以看到其定义,
>>> help(types.FunctionType)
Help on class function in module __builtin__:
class function(object)
| function(code, globals[, name[, argdefs[, closure]]])
|
| Create a function object from a code object and a dictionary.
| The optional name string overrides the name from the code object.
| The optional argdefs tuple specifies the default argument values.
| The optional closure tuple supplies the bindings for free variables.
这种方法可以更“动态”的实现需求,但动态也意味着一定的“危险性”与性能损失。具体采用哪种方式,就看最终的需求与权衡了。