在 python 中为动态函数添加装饰器
Add decorator to dynamic function in python
我的要求是复制函数代码,然后用新名称重命名,并根据 JSON 配置附加多个装饰器,然后将最终函数附加到模块。
我不想通过 JINJA2 模板来做,因为我必须生成 1.5k+ 方法签名来生成基于函数装饰器签名的 UI 组件。
最终结果应如下所示(所有字符串值均由配置驱动)
@component
@inport("IN", description="Packets to be written", type=str)
@inport("FILEPATH", description="File name", type=str)
@outport("OUT", required=False, description="Output port, if connected",type=str)
@must_run
def dynamicFuncNameBasedOnConfig(IN, FILEPATH, OUT):
"""
Write each packet from IN to a line FILEPATH, and also pass it
through to OUT.
"""
#some logic rest api calls
下面是只是一个示例,您可以通过这些操作来解决您的问题。你需要更多地思考你实际要解决的问题,然后你可以使用类似的策略。
如果函数体每次都需要不同,那就更难了。您可以使用内置 exec
函数之类的东西在 运行 时间将字符串转换为代码,但这并不安全(您让制作配置文件的人编写您的代码 -所以现在它可以做任何事情)而且对用户不友好(你还不如一开始就自己写)。不过,这听起来可能就是您要找的东西,因为您正在谈论为用户提供 运行 时间代码编辑器。
装饰器只是将函数作为输入的函数,因此您可以根据需要调用它们,而不是使用通常的语法来应用它们。例如,假设每个 dynamic_func_name_based_on_config
将使用相同的函数体(我将其称为 use_ports
作为示例),并且我们可以使用硬编码的参数名称,我们可以做一个一次性的像这样的版本:
dynamic_func_name_based_on_config = component(inport("IN", description="Packets to be written", type=str)(inport("FILEPATH", description="File name", type=str)(outport("OUT", required=False, description="Output port, if connected",type=str)(must_run(use_ports)))))
当然,这已经很难理解了,但是这个想法是,例如 inport("IN", description="Packets to be written", type=str)
给了我们一个函数,我们在 use_ports
上按顺序调用每个函数,所以所有这些调用是嵌套的。我们可以把这个逻辑放在一个函数中:
def apply_decorators(func, *decorators):
# Notice that with both the decorator syntax and with function calls, the
# last one that appears is the first to apply. So when we do this with a
# loop, we want to reverse the order.
for d in reversed(decorators):
func = d(func)
return func
dynamic_func_name_based_on_config = apply_decorators(
use_ports,
component,
inport("IN", description="Packets to be written", type=str),
inport("FILEPATH", description="File name", type=str),
outport("OUT", required=False, description="Output port, if connected", type=str),
must_run
)
并且假设这是一个通用模式,其中只有我们应用的 description
s 不同:
def make_described_port_user(in_desc, path_desc, out_desc):
# Create a version of use_ports that has the ports described via
# the inport/outport decorators, as well as applying the others.
return apply_decorators(
use_ports,
component,
inport("IN", description=in_desc, type=str),
inport("FILEPATH", description=path_desc, type=str),
outport("OUT", required=False, description=out_desc, type=str),
must_run
)
dynamic_func_name_based_on_config = make_described_port_user(
# Now we can just read these port descriptions from the config file, I guess.
"Packets to be written",
"File name",
"Output port, if connected"
)
最后,您可以通过修改 globals()
字典来设置该动态名称:
config_name = "dynamic_func_name_based_on_config" # we actually read it from the file
globals()[config_name] = make_described_port_user(
# similarly, fill in the arguments here
)
我的要求是复制函数代码,然后用新名称重命名,并根据 JSON 配置附加多个装饰器,然后将最终函数附加到模块。
我不想通过 JINJA2 模板来做,因为我必须生成 1.5k+ 方法签名来生成基于函数装饰器签名的 UI 组件。
最终结果应如下所示(所有字符串值均由配置驱动)
@component
@inport("IN", description="Packets to be written", type=str)
@inport("FILEPATH", description="File name", type=str)
@outport("OUT", required=False, description="Output port, if connected",type=str)
@must_run
def dynamicFuncNameBasedOnConfig(IN, FILEPATH, OUT):
"""
Write each packet from IN to a line FILEPATH, and also pass it
through to OUT.
"""
#some logic rest api calls
下面是只是一个示例,您可以通过这些操作来解决您的问题。你需要更多地思考你实际要解决的问题,然后你可以使用类似的策略。
如果函数体每次都需要不同,那就更难了。您可以使用内置 exec
函数之类的东西在 运行 时间将字符串转换为代码,但这并不安全(您让制作配置文件的人编写您的代码 -所以现在它可以做任何事情)而且对用户不友好(你还不如一开始就自己写)。不过,这听起来可能就是您要找的东西,因为您正在谈论为用户提供 运行 时间代码编辑器。
装饰器只是将函数作为输入的函数,因此您可以根据需要调用它们,而不是使用通常的语法来应用它们。例如,假设每个 dynamic_func_name_based_on_config
将使用相同的函数体(我将其称为 use_ports
作为示例),并且我们可以使用硬编码的参数名称,我们可以做一个一次性的像这样的版本:
dynamic_func_name_based_on_config = component(inport("IN", description="Packets to be written", type=str)(inport("FILEPATH", description="File name", type=str)(outport("OUT", required=False, description="Output port, if connected",type=str)(must_run(use_ports)))))
当然,这已经很难理解了,但是这个想法是,例如 inport("IN", description="Packets to be written", type=str)
给了我们一个函数,我们在 use_ports
上按顺序调用每个函数,所以所有这些调用是嵌套的。我们可以把这个逻辑放在一个函数中:
def apply_decorators(func, *decorators):
# Notice that with both the decorator syntax and with function calls, the
# last one that appears is the first to apply. So when we do this with a
# loop, we want to reverse the order.
for d in reversed(decorators):
func = d(func)
return func
dynamic_func_name_based_on_config = apply_decorators(
use_ports,
component,
inport("IN", description="Packets to be written", type=str),
inport("FILEPATH", description="File name", type=str),
outport("OUT", required=False, description="Output port, if connected", type=str),
must_run
)
并且假设这是一个通用模式,其中只有我们应用的 description
s 不同:
def make_described_port_user(in_desc, path_desc, out_desc):
# Create a version of use_ports that has the ports described via
# the inport/outport decorators, as well as applying the others.
return apply_decorators(
use_ports,
component,
inport("IN", description=in_desc, type=str),
inport("FILEPATH", description=path_desc, type=str),
outport("OUT", required=False, description=out_desc, type=str),
must_run
)
dynamic_func_name_based_on_config = make_described_port_user(
# Now we can just read these port descriptions from the config file, I guess.
"Packets to be written",
"File name",
"Output port, if connected"
)
最后,您可以通过修改 globals()
字典来设置该动态名称:
config_name = "dynamic_func_name_based_on_config" # we actually read it from the file
globals()[config_name] = make_described_port_user(
# similarly, fill in the arguments here
)