a little more explicative error message?

Terry Reedy tjreedy at udel.edu
Tue Jul 16 10:25:06 CEST 2013

On 7/16/2013 1:44 AM, Vito De Tullio wrote:
> Hi
> I was writing a decorator and lost half an hour for a stupid bug in my code,
> but honestly the error the python interpreter returned to me doesn't
> helped...
> $ python3
> Python 3.3.0 (default, Feb 24 2013, 09:34:27)
> [GCC 4.7.2] on linux
> Type "help", "copyright", "credits" or "license" for more information.
>>>> from functools import wraps
>>>> def dec(fun):
> ...  @wraps
> ...  def ret(*args, **kwargs):
> ...   return fun(*args, **kwargs)

At this point, when dec(fun) is called, the interpreter *successfully* 
executes ret = wraps(ret), which works, but is wrong, because wraps 
should be called with the wrapped function fun as its argument, not the 
wrapper function ret. The interpreter should instead execute
ret = partial(update_wrapper, wrapped = fun, ...)(ret)
which will update ret to look like fun.

> ...  return ret
> ...
>>>> @dec
> ... def fun(): pass

At this point, the interpreter *successfully* executes fun = dec(fun) = 
(wraps(ret))(fun), which causes ret = wraps(ret) before returning ret. 
Notice that when dec returns, the wraps code is over and done with. 
Instead the interpreter should execute
fun = (partial(update_wrapper, wrapped = fun, ...)(ret))(fun)

> ...
>>>> fun()
> Traceback (most recent call last):
>    File "<stdin>", line 1, in <module>
> TypeError: update_wrapper() missing 1 required positional argument:
> 'wrapper'

Because fun has not been properly wrapped because you left out a call.

> Soo... at a first glance, no tricks... can you tell where is the error? :D

Not exactly, but it must have something to do with wraps, so the first 
thing I would do is to look at the wraps doc if I had not before. It 
only takes a couple of minutes.

 >>> from functools import wraps
 >>> help(wraps)
Help on function wraps in module functools:

wraps(wrapped, assigned=('__module__', '__name__', '__qualname__', 
'__doc__', '__annotations__'), updated=('__dict__',))
     Decorator factory to apply update_wrapper() to a wrapper function

     Returns a decorator that invokes update_wrapper() with the decorated
        function as the wrapper argument and the arguments to wraps() as
        the remaining arguments. ...

This is pretty clear that wraps is not a decorator but a function that 
returns  decorator, which means that is must be called with an argument 
in the @del statement.

To really understand what is intended to happen so I could write the 
commentary above, I looked at the code to see
def wraps(wrapped, ...):
     return partial(update_wrapper, wrapped=wrapped,
                    assigned=assigned, updated=updated)

> As I said, the error is totally mine, I just forgot to pass the function as
> parameter to wraps. But... what is "update_wrapper()"? and "wrapper"? There
> is no useful traceback or something... just... this.
> Ok, the documentation clearly says:
>      This is a convenience function to simplify applying partial() to
>      update_wrapper().
> So, again, shame on me... I just read carefully the doc *after* 20 minutes
> trying everything else...  still... I think should be useful if wraps()
> intercept this error saying something more explicit about the missing fun
> parameter...

How is a function that has already been called and has returned without 
apparent error supposed to catch a later error caused by its return not 
being correct, due to its input not being correct?

Your request is like asking a function f(x) to catch ZeroDivisionError 
if it return a 0 and that 0 is later used as a divisor after f returns, 
as in 1/f(x).

When you call wraps with a callable, there is no way for it to know that 
the callable in intended to the be the wrapper instead of the wrappee, 
unless it were clairvoyant ;-).

Terry Jan Reedy

More information about the Python-list mailing list