Question

I have a set of functions/methods that all have different set of positional arguments and- in some cases - keyword arguments too, but share one string argument named lane_type. It may be either positional or keyword argument. Due to design flaw (guilty as charged) the same value in different places may have capital letters in the string. Therefore for comparisons I have to convert it to lower case. Eventually I decided to try and make the conversion through the decorator:

def lt_dec(func):
    def _lt_lower(**kwargs):
        kwargs['lane_type'] = kwargs['lane_type'].lower()
        return func(**kwargs)
    return _lt_lower

Of course, I tried to test it - and it failed:

In [38]: @lt_dec
def test1(lane_type):
    print lane_type
   ....:     

In [39]: test1('Solid')
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
/homes/markg/<ipython-input-39-bb6cef5c7fad> in <module>()
----> 1 test1('Solid')

TypeError: _lt_lower() takes exactly 0 arguments (1 given)

Obviously, I do not fully understand **kwargs substitution mechanism. I will really appreciate an explanation what I am missing and if there is an elegant solution for my problem.

EDIT:

I believe I should show some examples of function definitions for clarity: Here is one method from a class

def add_polygon(self, points, lane_type, color):

A method from another class:

def __init__(self, points, lane_type, color, side=None):

A function

def lane_weight(lane_type):

Of course, for a function with 1 argument (I have several of those) the solution is simple

def lt_dec(func):
    def _lt_lower(lane_type):
        return func(lane_type)
    return _lt_lower

As is have stated above - is there is a generic solution that for all of the examples shown? (I am ready to accept No as answer)

Was it helpful?

Solution

Using **kwargs in function definition, you get keyword arguments (not positional arguments) as a dictionary:

>>> def lt_dec(func):
...     def _lt_lower(**kwargs):
...         print 'type(kwargs)=', type(kwargs)
...         print 'kwargs=', kwargs
...         kwargs['lane_type'] = kwargs['lane_type'].lower()
...         return func(**kwargs)
...     return _lt_lower
...
>>> @lt_dec
... def test1(lane_type):
...     print lane_type
...
>>> test1(lane_type='Solid')
type(kwargs)= <type 'dict'>
kwargs= {'lane_type': 'Solid'}
solid

>>> def lt_dec(func):
...     def _lt_lower(*args, **kwargs):
...         if args:
...             args = (args[0].lower(),) + args[1:]
...         elif 'lane_type' in kwargs:
...             kwargs['lane_type'] = kwargs['lane_type'].lower()
...         return func(*args, **kwargs)
...     return _lt_lower
...
>>> @lt_dec
... def test1(lane_type):
...     print lane_type
...
>>> test1('Solid')
solid
>>> test1(lane_type='Solid')
solid

UPDATE

Using inspect.getcallargs:

>>> import inspect
>>>
>>> def lt_dec(func):
...     def _lt_lower(*args, **kwargs):
...         kwargs = inspect.getcallargs(func, *args, **kwargs)
...         if 'lane_type' in kwargs:
...             kwargs['lane_type'] = kwargs['lane_type'].lower()
...         return func(**kwargs)
...     return _lt_lower
...
>>> @lt_dec
... def test1(blah, lane_type):
...     print lane_type
...
>>> test1('foo', 'Solid')
solid
>>> test1('foo', lane_type='Solid')
solid

OTHER TIPS

This is the version of decorator that reduces impact of inspect by using it at decoration time - and not at the execution time. The execution impact is still relatively high - but 100 times lower than in the original decorator you proposed (just FYI)

def lane_type_lower(func):

    def _conv_pos(*args, **kwargs):
        args = list(args);
        args[pos] = args[pos].lower()
        return args, kwargs

    def _conv_kw(*args, **kwargs):
        kwargs['lane_type'] = kwargs['lane_type'].lower()
        return args, kwargs

    insp = inspect.getargspec(func)
    try:
        pos = insp.args.index('lane_type')
        conv_func = _conv_pos
    except ValueError:
        conv_func = _conv_kw

    def _lane_type_lower(*args, **kwargs):
        args, kwargs = conv_func(*args, **kwargs)
        return func(*args, **kwargs)

    return _lane_type_lower
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top