简体   繁体   中英

Add decorator to dynamic function in python

My requirement is copy function code then rename with new name and append multiple decorator based on JSON config then append that final function to module.

I don't want to do via JINJA2 template, because I have to generate 1.5k+ method signatures to generate UI component based on function decorator signature.

Final outcome should look like this (all string values are config driven)

@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 

The below is just an example of the kinds of manipulation you can do to solve your problem. You need to think much more about the problem you are actually trying to solve, and then you can use similar strategies.

If the function body needs to be different each time then that's much harder. You can use things like the built-in exec function to turn a string into code at runtime, but this is not secure (you are letting the person who makes the config file, write your code - so now it can do anything) and not user-friendly (you might as well have just written it yourself in the first place). It sounds like it might be what you're looking for, though, since you're talking about giving the user a run-time code editor.


Decorators are just functions that take a function as input, so instead of applying them with the usual syntax, you can call them as needed. Supposing for example that each dynamic_func_name_based_on_config will use the same function body (I'll call it use_ports for the example), and that we can use hard-coded parameter names, we can do a one-off version like this:

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)))))

Of course, that's already really hard to follow, but the idea is that for example inport("IN", description="Packets to be written", type=str) gives us a function, and we call each function in order on use_ports , so all those calls are nested. We can put that logic in a function:

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
)

And supposing that this is a general pattern where only the description s we're applying varies:

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"
)

Finally, you can set that dynamic name by modifying the globals() dict:

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
)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM