bc291
bc291

Reputation: 1171

Using decorators to supply new args, kwargs

I have done some research about decorators, mainly about typical use-cases. It turned out that in majority of cases, they act as validators, timers, permission checkers - pretty transparent stuff. I was wondering if it would be pythonic to use them to precalculate and supply some variables to decorated function.

I've on my mind situation like this:

Decorator:

def foo(fn):
    @functools.wraps(fn)
    def wrapper(*args, **kwargs):
        fn_name = fn.__name__
        var = Test._do_something(Test.store_dict[fn_name])
        kw_args = {**kwargs, **dict(var=var)}
        return fn(*args, **kw_args)
    return wrapper

Class:

class Test:
    store_dict = dict(fn1='fn1_operator',
                      fn2='fn2_operator')

    @staticmethod
    @foo
    def fn1(dummy_data, **kwargs):
        return Test.__handle_data(dummy_data=dummy_data,
                                  **kwargs)
    @staticmethod
    @foo
    def fn2(dummy_data, **kwargs):
        return Test.__handle_data(dummy_data=dummy_data,
                                  **kwargs)

    @staticmethod
    def _do_something(var):
        return var

    @staticmethod
    def __handle_data(dummy_data, var):
        return (dummy_data, var)

Usage:

test_instance = Test()
test_instance.fn1(dummy_data='test')

As you can see fn1 and fn2 methods are doing almost same thing (calling __handle_data method), but with different var. Variable var depends on called function name. Calling fn1 results with var=fn1_operator and so on. Having in my mind the Zen of Python:

Simple is better than complex.

I'm having doubts about that being pythonic.

In the other hand, without that decorator, my code would have a lot of repetitions:

@staticmethod
def fn1(dummy_data):
    var = _do_something('fn1')
    return Test.__handle_data(dummy_data=dummy_data,
                              var=var)
@staticmethod
def fn2(dummy_data):
    var = _do_something('fn2')
    return Test.__handle_data(dummy_data=dummy_data,
                              var=var)

Is it correct? What should I change?

Upvotes: 2

Views: 62

Answers (1)

chepner
chepner

Reputation: 530960

I wouldn't use a decorator here; I'd write a function that returns a closure over the specific data instead.

def make_test(arg):
    @staticmethod
    def _(dummy_data):
        var = _do_something(arg)
        return Test.__handle_data(dummy_data, var=var)
    return _

fn1 = make_test('fn1')
fn2 = make_test('fn2')

(I didn't test, but I'm pretty sure it works the same if you decorate the closure as shown, or simply return an undecorated function and write fn1 = staticmethod(make_test('fn1')), etc.)


With a decorator that focuses on the what is common, it might look like

def foo(f):
    def _(dummy_data):
        var = _do_something(f())
        return Test.__handle_data(dummy_data, var=var)
    return _

@foo
def fn1(self):
    return 'fn1'


@foo
def fn2(self):
    return 'fn2'

which has the same behavior, but is less clear as to the intent. Too much is hidden in the decorator.

Upvotes: 2

Related Questions