I've got a function that takes args and kwargs, and I need to do something in my decorator based on the value of the 2nd arg in the function, like 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 the function with the second argument as a regular argument OR a keyword-argument, so if the user calls my_func
with arg2
as a 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 just doing a try/except
and catch the IndexError
?
I found an answer using the python decorator
package. One feature of this package is that it preserves positional/keyword args no matter how the user passes them. It has the added benefit of reducing a lot of code, so my original code:
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))
This is the most robust way that I can think of to handle it... The trick is to inspect the name of the second argument. Then, in the decorator, you check to see if that name is present in kwargs
. If yes, then you use that. If no, 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)
running this results in:
foo
foo
no foo for you
no foo for you
as expected.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With