Search code examples
pythondecoratorsubroutine

How to apply a decorator to subroutines in python


I want to modify the way several of my functions behave, so the use of a decorator comes to mind. For example let's say I have a batch data taking function takeData(s):

def takeData(s):
    takeDataSet_1(s)
    takeDataSet_2(s)
    .
    .
    .

A simple thing that I might want to do is update the parameter dict s, before each of the takeDataSet function calls. So that the effective code would be more like this:

def takeData(s):
    s = updateParams(s)
    takeDataSet_1(s)
    s = updateParams(s)
    takeDataSet_2(s)
    s = updateParams(s)
    .
    .
    .

Is there a way to do this with a decorator so that my code would look more like

@takeDataWithUpdatedParams
def takeData(s):
    takeDataSet_1(s)
    takeDataSet_2(s)

Is there a way to control the depth of recursion such a decorator? So that if takeDataSet_1(s) had subroutines of its own s could be updated between them, as in:

@recursiveUpdateParams
def takeData(s):
    takeDataSet_1(s)
    takeDataSet_2(s)

def takeDataSet_1(s):
    takeData_a(s)
    takeData_b(s)

Gets executed as

def takeData(s):
    s = updateParams(s)
    takeDataSet_1(s)
    s = updateParams(s)
    takeDataSet_2(s)
    s = updateParams(s)

def takeDataSet_1(s):
    s = updateParams(s)
    takeData_a(s)
    s = updateParams(s)
    takeData_b(s)

Solution

  • Very interesting question. To achieve this, you need to dive deep into the function object (without eval, exec or ast stuff, anyway).

    def create_closure(objs):
        creat_cell = lambda x: (lambda: x).__closure__[0]
        return tuple(create_cell(obj) for obj in objs)
    

    def hijack(mapper):
        from types import FunctionType
        def decorator(f):
            globals_ = {k: mapper(v) for k, v in f.__globals__.items()}
            closure_ = f.__closure__
            if closure_:
                closure_ = create_closure(i.cell_contents for i in closure_)
            return (lambda *arg, **kwarg:
                    FunctionType(f.__code__, globals_, f.__name__,
                                 f.__defaults__, closure_)(*arg, **kwarg))
        return decorator
    

    Test:

    x = 'x'
    y = 'y'
    @hijack(lambda obj: 'hijacked!' if obj is x else obj)
    def f():
        return (x, y)
    f()
    

    Out:

    ('hijacked!', 'y')
    

    Finally a solution to the original problem:

    x = lambda: 'x()'
    y = lambda: 'y()'
    mydecorator = lambda f: lambda *arg, **kwarg: f(*arg, **kwarg) + ' decorated!'
    targets = {id(x): mydecorator(x)}
    def mapper(obj):
        if id(obj) in targets:
            return targets[id(obj)]
        else:
            return obj
    @hijack(mapper)
    def f():
        return (x(), y())
    f()
    

    Out:

    ('x() decorated!', 'y()')