Parsing args and kwargs in decorators
Question: 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`?


Answer: 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.

