pythonmetaprogrammingdecoratorpython-decoratorsself-modifying

Is there a way to decorate/perform operations on every line of a function in Python?


Say I have a function or method that does something repetitive, like checking a value, before performing every operation it does, like so:

def myfunc():
    if mybool:
        do_operation_1()
    else:
        return

    if mybool:
        do_operation_2()
    else:
        return

    ...

These checks get repetitive, and end up wasting a lot of time and keyboard springs, especially when they are needed very often.

If you have control over the operation functions, like, do_operation_N you can decorate the functions with something that checks the boolean.

But what if you don't have control over the individual do_operation_N operations? If, for each line in a function or method, I want the same check to be performed, is there some way to "insert" it without explicitly writing it in on each operation line? For example, is there some decorator magic by which I could do the following?

def magic_decorator(to_decorate):
    def check(*args, **kwargs):
        for call in to_decorate: #magic
            if mybool:
                to_decorate.do_call(call) #magic
            else:
                return #or break, raise an exception, etc
    return check

@magic_decorator
def myfunc():
    do_operation_1()
    do_operation_2()
    ...

If there is a way to achieve this, I don't care if it uses decorators or not; I just want some way to say "for every line in function/method X, do Y first".

The "magic" example of a do_call method above is shorthand for what I'm after, but it would encounter serious problems with out-of-order execution of individual lines (for example, if a function's first line was a variable assignment, and its second was a use of that variable, executing them out of order would cause problems).

To be clear: the ability to externally control the line-by-line order of a function's execution is not what I'm trying to achieve: ideally, I'd just implement something that, in the natural execution order, would perform an operation each time myfunc does something. If "does something" ends up being limited to "calls a function or method" (excluding assignments, if checks, etc), that is fine.


Solution

  • Store your operations in a sequence, then use a loop:

    ops = (do_operation_1, do_operation_2, do_operation_3)
    
    for op in ops:
        if mybool:
            op()
        else:
            return