Использование Python-декораторов для включения / выключения определенных частей программы - PullRequest
0 голосов
/ 07 сентября 2018

У меня есть несколько сценариев, которые, по сути, являются этапами подготовки данных для настройки данных для имитационных моделей. Я очень часто хочу запускать только его части, скажем, «фаза1» или «фаза2», но большинство «фаз» занимают более одной строки, поэтому комментировать не очень удобно. Так я обычно делаю:

# Phase 1
if True:
  do_step_1('high')
  do_step_2()
  for i in range(1,10):
    do_step_3()

#Phase 2
if True:
  do_step_1('low')
  do_something_else()

А затем при необходимости измените True на False.

Теперь, это довольно громоздко. Иногда фазы зависят друг от друга (поэтому, когда я запускаю 3, мне также нужно запустить 1), они являются вложенными и т. Д.

То, что я хочу сделать, - это каким-то образом передать аргумент в мой сценарий, который будет запускать одну или несколько «фаз», и мне нужен какой-то способ, чтобы «пометить» определенные функции, блоки кода или области как часть этой «фазы». Определенный фрагмент кода может быть частью нескольких фаз, так что, когда есть фрагменты B и C, которые зависят от фрагмента A, я мог бы пометить A как часть 'phase1' и 'phase2', а затем, когда я запускаю phase1, он будет запускать чанк A и чанк B, а для фазы 2, чанк A и чанк C. Надеюсь, это все еще имеет смысл.

Так что я подумал, что декораторы были бы идеальными для этого, чтобы я мог (концептуально) сделать что-то вроде

@partOfAPhase("phase1", "phase2")
def f1():
    pass

и затем каким-то образом я передаю список «фаз» для запуска моей программе (либо из командной строки, либо путем установки ее в качестве переменной конфигурации где-нибудь), и когда моя программа запускается, она выполняет только те функции, которые были оформлены как часть одного из этапов, указанных для запуска.

Итак, , мне нужен общий декоратор, который можно применять к функциям или функциям-членам, которые принимают любое количество аргументов, и мне нужно иметь возможность передавать список тегов. 'самому декоратору. Затем внутри декоратора мне нужно проверить (когда вызывается исходная функция или член), существуют ли теги этого декоратора в глобальном (может быть, статическом классе?) Списке тегов для запуска.

Я посмотрел на https://gist.github.com/Zearin/2f40b7b9cfc51132851a, и, кажется, в самом конце, чтобы сделать более или менее то, что я хочу, все же я не могу полностью разобрать все части вместе, чтобы сделать то, что я хочу. Точнее говоря, я не совсем понимаю генератор с двумя вложенными декораторами и то, понадобятся ли мне две функции или только одна для его реализации, а также как я получу доступ к аргументу, который передается декоратору (то есть к этапам запуска ).

Ответы [ 2 ]

0 голосов
/ 07 сентября 2018

Вот что я закончил, узнав, что метод, представленный в ссылке, очень запутанный, и это гораздо проще сделать с помощью вызываемого объекта декоратора. Это работает как с автономными функциями, так и с методами, которые принимают любое количество аргументов (с некоторыми простыми тестами, которые просто выводят легко визуально проверенные результаты):

import functools
import sys

class runConditional(object):
    def __init__(self, datasets):
        self.datasets = datasets

    def __call__(self, func):
        def wrapped_f(*args, **kwargs):
            global to_run
            for d in self.datasets:
                if d in to_run:
                    sys.stdout.write(" 1")
                    func(*args, **kwargs)
                    return
            sys.stdout.write(" 0")

        return wrapped_f

@runConditional([1])
def fun1():
    pass

@runConditional([2])
def fun2():
    pass

@runConditional([1,2,3])
def fun3(arg1, arg2):
    pass

def fun_always():
    sys.stdout.write(" 1")
    pass

@runConditional([])
def fun_never():
    pass

class test():
    @runConditional([1])
    def m1(self):
        pass

    @runConditional([2])
    def m2(self):
        pass

    @runConditional([1,2,3])
    def m3(self, arg1):
        pass

    def m_always(self):
        sys.stdout.write(" 1")
        pass

    @runConditional([])
    def m_never(self):
        pass

def run_test(funcs_to_run, expected):
    global to_run
    t = test()
    funcs = [ fun1, fun2, functools.partial(fun3, "123", "meh"), fun_always, fun_never,
            t.m1, t.m2, functools.partial(t.m3, "321"), t.m_always, t.m_never ]
    to_run = funcs_to_run
    print "Expected: " + " ".join(map(str, expected))
    sys.stdout.write("Actual:  ")
    for f in funcs:
        f()
    print ""
    print ""

run_test([2],       [ 0, 1, 1, 1, 0,  0, 1, 1, 1, 0 ])
run_test([1],       [ 1, 0, 1, 1, 0,  1, 0, 1, 1, 0 ])
run_test([],        [ 0, 0, 0, 1, 0,  0, 0, 0, 1, 0 ])
run_test([1, 2],    [ 1, 1, 1, 1, 0,  1, 1, 1, 1, 0 ])
0 голосов
/ 07 сентября 2018

Не уверен, что это соответствует вашим потребностям, но вот быстрое и грязное подтверждение концепции:

# This first part could go in its own module or a class or whatever
_program = []

def partOfPhase(*phases):
    def decorator(fn):
        _program.append((fn, tuple(phases)))
        return fn
    return decorator

def partOfProgram(fn):
    _program.append((fn, None))
    return fn

def runProgram(phase):
    for fn, fn_phases in _program:
        if fn_phases is None or any(p in fn_phases for p in phases):
            fn()

# This is the actual script
import sys

@partOfPhase('phase1')
def step1():
    print('step1')

@partOfPhase('phase1', 'phase2')
def step2():
    print('step2')

@partOfProgram
def step3():
    print('step3')

@partOfPhase('phase2')
def step4():
    print('step4')

if __name__ == '__main__':
    phases = sys.argv[1:]
    runProgram(phases)

Например, если вы сохраните его как phases.py, вы получите:

> python phases.py
step3

> python phases.py phase1
step1
step2
step3

> python phases.py phase2
step3
step4

> python phases.py phase1 phase2
step1
step2
step3
step4

EDIT

Я думаю, что это, вероятно, больше похоже на то, о чем вы думали, функции, которые отключаются в зависимости от фазы:

# This first part could go in its own module or a class or whatever
from functools import wraps

_enabledPhases = []

def enablePhase(*phases):
    _enabledPhases.extend(phases)

def partOfPhase(*phases):
    def decorator(fn):
        @wraps(fn)  # Just "cosmetic" wrapping
        def decorated(*args, **kwargs):
            if any(p in phases for p in _enabledPhases):
                fn(*args, **kwargs)
        return decorated
    return decorator

# This is the actual script
import sys

@partOfPhase('phase1')
def step1():
    print('step1')

@partOfPhase('phase1', 'phase2')
def step2():
    print('step2')

def step3():
    print('step3')

@partOfPhase('phase2')
def step4():
    print('step4')

if __name__ == '__main__':
    phases = sys.argv[1:]
    enablePhase(*phases)
    step1()
    step2()
    step3()
    step4()
...