Добавить декоратор к динамической функции в Python - PullRequest
0 голосов
/ 15 декабря 2018

Мое требование - скопировать код функции, затем переименовать с новым именем и добавить несколько декораторов на основе конфигурации JSON, а затем добавить эту последнюю функцию в модуль.

Я не хочу делать это с помощью шаблона JINJA2, потому что мне нужно генерировать сигнатуры метода + 1,5 тыс. Для генерации компонента пользовательского интерфейса на основе сигнатуры декоратора функции.

Окончательный результат должен выглядеть следующим образом (все строковые значения определяются конфигурацией)

@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 голосов
/ 15 декабря 2018

Ниже приведен только пример видов манипуляций, которые вы можете сделать, чтобы решить вашу проблему.Вам нужно больше думать о проблеме, которую вы на самом деле пытаетесь решить, и тогда вы можете использовать аналогичные стратегии.

Если тело функции каждый раз должно отличаться, тогда это гораздо сложнее.Вы можете использовать такие вещи, как встроенная функция 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() 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
)
...