tags:

views:

76

answers:

4

If I have to wrap an existing method, let us say wrapee() from a new method, say wrapper(), and the wrapee() provides default values for some arguments, how do I preserve its semantics without introducing unnecessary dependencies and maintenance? Let us say, the goal is to be able to use wrapper() in place of wrapee() without having to change the client code. E.g., if wrapee() is defined as:

def wrapee(param1, param2="Some Value"):
  # Do something

Then, one way to define wrapper() is:

def wrapper(param1, param2="Some Value"):
  # Do something
  wrapee(param1, param2)
  # Do something else.

However, wrapper() has to make assumptions on the default value for param2 which I don't like. If I have the control on wrapee(), I would define it like this:

def wrapee(param1, param2=None):
  param2 = param2 or "Some Value"
  # Do something

Then, wrapper() would change to:

def wrapper(param1, param2=None):
  # Do something
  wrapee(param1, param2)
  # Do something else.

If I don't have control on how wrapee() is defined, how best to define wrapper()? One option that comes into mind is to use to create a dict with non-None arguments and pass it as dictionary arguments, but it seems unnecessarily tedious.

Update:

The solution is to use both the list and dictionary arguments like this:

def wrapper(param1, *args, **argv):
  # Do something
  wrapee(param1, *args, **argv)
  # Do something else.

All the following calls are then valid:

wrapper('test1')
wrapper('test1', 'test2')
wrapper('test1', param2='test2')
wrapper(param2='test2', param1='test1')
+3  A: 

Check out argument lists in the Python docs.

>>> def wrapper(param1, *stuff, **args):
...  print(param1)
...  print(stuff)
...  print(args)
...
>>> wrapper(3, 4, 5, foo=2)
3
(4, 5)
{'foo': 2}

Then to pass the args along:

wrapee(param1, *stuff, **args)

The *stuff is a variable number of non-named arguments, and the **args is a variable number of named arguments.

Amber
That won't make the new method completely compatible. While I can say wrapee(param2="value", param1), I won't be able to do the same with wrapper().
haridsv
If you define wrapper() as `def wrapper(**args)`, then `args` will be a dictionary of all of the named arguments provided.
Amber
This won't work with the callers that make assumption on the position of the param2, e.g., wrapee(param1, param2) (they are forced to use named parameters).
haridsv
`wrappee(param2="value", param1)` isn't even valid (non-kwarg after kwarg). Could you provide an example of what you're talking about?
Amber
Actually, this should work, I missed to see that you use using both * and ** form. I also made a mistake in my first comment, you have to pass the second parameter also named, otherwise it becomes syntactic error.
haridsv
A: 
def wrapper(param1, param2=None):
    if param2:
        wrapee(param1, param2)
    else:
        wrapee(param1)
Christopher Bruns
Though it is practical for this specific example, I am looking for a generic solution (e.g., what if there are several parameters with defaults?).
haridsv
A: 

is this what you want?

#!/usr/bin/python

from functools import wraps
def my_decorator(f):
    @wraps(f)
    def wrapper(*args, **kwds):
        print 'Calling decorated function'
        return f(*args, **kwds)
    return wrapper


def f1(x, y):
  print x, y

def f2(x, y="ok"):
  print x, y

my_decorator(f1)(1,2)
my_decorator(f2)(1,2)
my_decorator(f2)(1)

adapted from http://koala/doc/python2.6-doc/html/library/functools.html#module-functools

Dyno Fu
+3  A: 

I'd hardly say that it isn't tedious, but the only approach that I can think of is to introspect the function that you are wrapping to determine if any of its parameters have default values. You can get the list of parameters and then determine which one is the first that has default values:

from inspect import getargspec

method_signature = getargspec(method)
param_names = method_signature[0]
default_values = method_signature[3]
params = []

# If any of method's parameters has default values, we need
# to know the index of the first one that does.
param_with_default_loc = -1
if default_values is not None and len(default_values) > 0:
    param_slice_index = len(default_values) * -1
    param_with_default = param_names[param_slice_index:][0]
    param_with_default_loc = param_names.index(param_with_default)

At that point, you can iterate over param_names, copying into the dict that is passed to wrappee. Once your index >= param_with_default_loc, you can obtain the default values by looking in the default_values list with an index of your index - param_with_default_loc.

Does that make any sesne?

Of course, to make this generic, you would to define it as a wrapper function, adding yet another layer of wrapping.

Adam Crossland
Pretty useful way to introspect a method, thanks for sharing.
haridsv