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.

这种方法可以更“动态”的实现需求,但动态也意味着一定的“危险性”与性能损失。具体采用哪种方式,就看最终的需求与权衡了。

参考