在工程开发中有这么一个需求,要求用户可以在前端编写python函数,要求实现以下功能
1、使用Python后端可以执行
2、需要校验用户的入参格式是否正确
3、用户编写函数的默认值在前端展示的功能
基础知识:
在Python中,一个函数对象的__annotations__和__defaults__属性分别代表该函数的入参类型和入参默认值。使用__code__.co_varnames可以获取到函数中的变量(返回一个元祖),结合__code__.co_argcount可以获取参数个数,通过下标就可以拿到入参。
代码如下:
# 这是一个demo函数
def demo(domain, url, method, params, header: Optional[Dict[str, Any]] = {}):
a=1
# 输出:('domain', 'url', 'method', 'params', 'header', 'a')
print(demo.__code__.co_varnames)
# 输出:5
print(demo.__code__.co_argcount)
# 输出:{'header': typing.Optional[typing.Dict[str, typing.Any]]}
print(demo.__annotations__)
# 输出:({},)
print(demo.__defaults__)
当我们的函数变成一个字符串时,将其编译并创建一个函数对象时,依然可以通过这些属性拿到函数的入参、入参默认值、入参类型。
实现代码:
# 将字符串转函数
def str2funcion(func_str):
"""
将字符串编译成可执行函数
:param func_str:
:return:
"""
# 编译字符串(compile编译出来的function的作用域是module)
function = compile(func_str, '', 'exec')
# 创建一个空命名空间
globals_dict = {}
# 在该命名空间中执行该函数
exec(function, globals_dict)
# 获取可执行函数(获取module下的code对象,才是我们通过字符串转换出来的可执行函数)
for const in function.co_consts:
if isinstance(const, CodeType):
function = const
# 通过函数名获取到函数原本的annotations(参数类型)和defaults(参数默认值)
original_function = globals_dict[function.co_name]
annotations = original_function.__annotations__
defaults = original_function.__defaults__
# 将code对象封装成FunctionType对象,才可以被执行和调用
function = FunctionType(function, globals())
# 赋值之前的annotations和defaults, 解决FunctionType转换成可执行函数导致annotations和defaults丢失
function.__annotations__ = annotations
function.__defaults__ = defaults
return function
def get_annotation_by_function(function) -> dict:
"""
获取函数入参和入参类型
:param function:
:return:
"""
if isinstance(function, str):
# 将字符串编译成可执行函数(compile编译出来的function的作用域是module)
function = compile(function, '', 'exec')
# 创建一个空命名空间
globals_dict = {}
# 在该命名空间中执行该函数
exec(function, globals_dict)
# 获取可执行函数(获取module下的code对象,才是我们通过字符串转换出来的可执行函数)
for const in function.co_consts:
if isinstance(const, CodeType):
function = const
# 通过函数名获取到函数原本的annotations(参数类型)和defaults(参数默认值)
original_function = globals_dict[function.co_name]
annotations = original_function.__annotations__
defaults = original_function.__defaults__
# 将code对象封装成FunctionType对象,才可以被执行和调用
function = FunctionType(function, globals())
# 赋值之前的annotations和defaults, 解决FunctionType转换成可执行函数导致annotations和defaults丢失
function.__annotations__ = annotations
function.__defaults__ = defaults
params = {}
# 获取函数入参
parameters = inspect.signature(function).parameters
for param_name, parameter in parameters.items():
param_type = parameter.annotation
params[param_name] = param_type
return params
def get_params_by_function(function) -> dict:
"""
获取函数入参和默认值
:param function:
:return:
"""
if isinstance(function, str):
# 将字符串编译成可执行函数(compile编译出来的function的作用域是module)
function = compile(function, '', 'exec')
# 创建一个空命名空间
globals_dict = {}
# 在该命名空间中执行该函数
exec(function, globals_dict)
# 获取可执行函数(获取module下的code对象,才是我们通过字符串转换出来的可执行函数)
for const in function.co_consts:
if isinstance(const, CodeType):
function = const
# 通过函数名获取到函数原本的annotations(参数类型)和defaults(参数默认值)
original_function = globals_dict[function.co_name]
annotations = original_function.__annotations__
defaults = original_function.__defaults__
# 将code对象封装成FunctionType对象,才可以被执行和调用
function = FunctionType(function, globals())
# 赋值之前的annotations和defaults, 解决FunctionType转换成可执行函数导致annotations和defaults丢失
function.__annotations__ = annotations
function.__defaults__ = defaults
print(function.__code__.co_varnames)
params = {}
# 获取函数入参
parameters = inspect.signature(function).parameters
for param_name, parameter in parameters.items():
param_type = parameter.annotation
param_default = parameter.default
if param_default is inspect._empty:
params[param_name] = None
else:
params[param_name] = param_default
return params