Parsing arguments and quargs in decorators

I have a function that accepts args and kwargs, and I need to do something in my decorator based on the value of the 2nd argument in the function, as in the code below:

def workaround_func(): def decorator(fn): def case_decorator(*args, **kwargs): if args[1] == 2: print('The second argument is a 2!') return fn(*args, **kwargs) return case_decorator return decorator @workaround_func() def my_func(arg1, arg2, kwarg1=None): print('arg1: {} arg2: {}, kwargs: {}'.format(arg1, arg2, kwarg1)) 

The problem is that python allows users to call a function with a second argument as a regular argument OR a keyword argument, so if a user calls my_func with arg2 like kwarg, it raises an IndexError , see below:

 In [8]: d.my_func(1, 2, kwarg1=3) The second argument is a 2! arg1: 1 arg2: 2, kwargs: 3 In [9]: d.my_func(1, arg2=2, kwarg1=3) --------------------------------------------------------------------------- IndexError Traceback (most recent call last) <ipython-input-9-87dc89222a9e> in <module>() ----> 1 d.my_func(1, arg2=2, kwarg1=3) /home/camsparr/decoratorargs.py in case_decorator(*args, **kwargs) 2 def decorator(fn): 3 def case_decorator(*args, **kwargs): ----> 4 if args[1] == 2: 5 print('The second argument is a 2!') 6 return fn(*args, **kwargs) IndexError: tuple index out of range 

Is there a way around this without doing try/except and catching an IndexError ?

+4
source share
3 answers

I found the answer using python decorator package. One of the features of this package is that it stores the positional / keywords args no matter how the user passes them. It has the added benefit of reducing a lot of code, so my source code is:

 def workaround_func(): def decorator(fn): def case_decorator(*args, **kwargs): if args[1] == 2: print('The second argument is a 2!') return fn(*args, **kwargs) return case_decorator return decorator @workaround_func() def my_func(arg1, arg2, kwarg1=None): print('arg1: {} arg2: {}, kwargs: {}'.format(arg1, arg2, kwarg1)) 

becomes:

 from decorator import decorator @decorator def workaround_decorator(f, *args, **kwargs): if args[1] == 2: print('The second argument is 2!') return f(*args, **kwargs) @workaround_decorator def my_func(arg1, arg2, kwarg1=None): print('arg1: {} arg2: {}, kwargs: {}'.format(arg1, arg2, kwarg1)) 
+3
source

This is the most reliable way I can handle it ... The trick is to check the name of the second argument. Then in the decorator you check if this name is present in kwargs . If so, then you are using this. If not, then you use args .

 from inspect import getargspec def decorate(fn): argspec = getargspec(fn) second_argname = argspec[0][1] def inner(*args, **kwargs): special_value = (kwargs[second_argname] if second_argname in kwargs else args[1]) if special_value == 2: print "foo" else: print "no foo for you" return fn(*args, **kwargs) return inner @decorate def foo(a, b, c=3): pass foo(1,2,3) foo(1,b=2,c=4) foo(1,3,5) foo(1,b=6,c=5) 

Execution of this result:

 foo foo no foo for you no foo for you 

as was expected.

+3
source

Arg1 seems to be a required argument, but arg2 may be optional. You can change line 3 to:

  def case_decorator(arg1, arg2=None, *args, **kwargs): # or =0 or ='' whichever best 

and line 6:

  return fn(arg1, arg2, *args, **kwargs) 

If arg1 is also optional, change line 3 with arg1 = None or = 0 or = ''

0
source

All Articles