views:

77

answers:

4

I'd like to decorate a function, using a pattern like this:

def deco(func):
    def wrap(*a,**kw):
        print "do something"
        return func(*a,**kw)
    return wrap

The problem is that if the function decorated has a prototype like that:

def function(a,b,c): return

When decorated, the prototype is destroyed by the varargs, for example, calling function(1,2,3,4) wouldn't result in an exception. Is that a way to avoid that? How can define the wrap function with the same prototype as the decorated (func) one?

There's something conceptually wrong?

EDIT

My perverse idea was to lighten the "calling of the parent method" without modifying the signature. Something like

def __init__(self, something)
    super(ClassName, self).__init__(something)

to:

@extended
def __init__(self, something):
    ...

I was figuring out if this was possible and if this makes sense.

EDIT As Alex pointed out, the following code doesn't give an exception:

function(1,2,3,4)
A: 

By calling result=func(*a,**kw) first, you get the TypeError before printing "do something".

def deco(func):
    def wrap(*a,**kw):
        result=func(*a,**kw)
        print "do something"
        return result
    return wrap

@deco
def function(a,b,c): return

function(1,2,3)
# do something
function(1,2,3,4)
# TypeError: function() takes exactly 3 arguments (4 given)
unutbu
+3  A: 

The decorator module helps you create a decorator that preserves the function signature.

As a result, you will get the exception you expect when calling the function, and inspect.getargspec will give you the correct signature.

It works by dynamically building a function definition and using exec. Unfortunately, there isn't an easier built-in way to do this.

interjay
+4  A: 

You're wrong when you state that "calling function(1,2,3,4) wouldn't result in an exception". Check it out:

>>> def deco(f):
...   def w(*a, **k):
...     print 'do something'
...     return f(*a, **k)
...   return w
... 
>>> def f(a, b, c): return
... 
>>> f(1, 2, 3, 4)
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
TypeError: f() takes exactly 3 arguments (4 given)
>>> decorated = deco(f)
>>> decorated(1, 2, 3, 4)
do something
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "<stdin>", line 4, in w
TypeError: f() takes exactly 3 arguments (4 given)

As you see, you get exactly the same exception as when similarly calling the undecorated f (albeit after the print you've added).

For preserving the wrapped function's metadata (name, docstring, use functools.wraps. Predicting that the wrapped function will raise when called (to avoid doing other work before calling it) is always hard ("impossible" in general, as it's equivalent to the halting problem; "just hard" in specific, when you only care about raising a type-error for argument name and number mismatches and want to treat that specialized exception case differently from any other exception case -- a peculiar requirement;-0).

If you're adamant that you absolutely need it (and can perhaps explain why?) I'll be glad (well, not glad, but will grit my teeth and do it, after all I've got a long weekend in front of me;-) to take you down that labyrinthine path.

Alex Martelli
+1  A: 

Here is a trick that involves fetching the original argument specification from the decorated function, then creating a lambda by evaling a string with the same arguments. The decorator is then wrapped in this lambda thus to the outside world it has the same parameter names and default values:

import inspect, time
import functools

def decorator_wrapper(old_function, new_function):
    args, arglist, kw, default = inspect.getargspec(old_function)
    args = list(args)

    if arglist:
       args.append(arglist)

    if kw:
       args.append(kw)

    callstring = inspect.formatargspec(args, arglist, kw, default, formatvalue=lambda value: "")
    argstring = inspect.formatargspec(args, arglist, kw, default)[1:-1]

    unique_name = "_func" + str(int(time.time()))
    codestring = "lambda " + argstring + " : " + unique_name + callstring
    decorated_function = eval(codestring, {unique_name: new_function})

    return functools.wraps(old_function)(decorated_function)
truppo