将装饰器添加到python中的动态函数

时间:2018-12-15 08:29:23

标签: python jinja2 metaprogramming decorator modularity

我的要求是复制函数代码,然后使用新名称重命名并基于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 

1 个答案:

答案 0 :(得分:0)

以下是仅作为示例,可以用来解决问题。您需要对实际要解决的问题进行更多思考,然后才能使用类似的策略。

如果函数主体每次都需要不同,则要困难得多。您可以使用诸如内置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有所不同:

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
)