4

I have a function my cause exception and i want it to be a decorator. The code is as follow:

def des(i):
    def new_func(func):
        if i == 1:
            raise Exception
        else:
            return func
    return new_func


@des(1)
def func():
    print "!!"


if __name__ == '__main__':
    try:
        func()
    except Exception:
        print 'error'

but the output is:

Traceback (most recent call last):
  File "D:/des.py", line 10, in <module>
    @des(1)
  File "D:/des.py", line 4, in new_func
    raise Exception
Exception

so, how can I catch this exception?

LiGa
  • 159
  • 1
  • 2
  • 9
  • 1
    I believe this may be relevant to your issue: [catching exception in decorator whilst allowing caller to catch the exception](http://stackoverflow.com/questions/4249939/how-do-i-catch-an-exception-in-a-decorator-but-allow-the-caller-to-catch-it-as-w?rq=1) – user2412092 Aug 20 '13 at 04:57

4 Answers4

2

The exception is raised when you define the function. The only way to catch this exception would be:

try:
    @des(1)
    def func():
        print '!!'
except:
    print 'error'

If it's confusing why this is causing an exception, remember your code is equivalent to:

def func():
    print '!!'
func = des(1)(func)
# des(1) = new_func, so des(1)(func) is new_func(func)
Chris Barker
  • 2,279
  • 14
  • 15
  • but it lead func an undefined function – LiGa Aug 20 '13 at 04:58
  • Yes, if an exception is raised in the process of defining a function, that function will not be defined. There's really no way around that, other than to assign a different function to `func` in the `except` statement. I could help more if I knew what the actual code was, rather than this example. – Chris Barker Aug 20 '13 at 05:03
  • @LiGa Yes, but you can detect this via the exception and handle appropriately. – glglgl Aug 20 '13 at 05:30
2

So, right now your code basically boils down to this:

_des = des(1)

def _func();
    print '!!'

func = _des(func)

You're using the return value of des as the decorator, and I think that is causing the problem.

I think you might want to nest that returned function one more time:

def des(i): # container func.
    def new_func(func):
        def ret_func(*args, **kwargs):
            if i == 1:
                raise Exception
            else:
                return func(*args, **kwargs)

        return ret_func # return the func with the bound variable
    return new_func # return the func which creates the function w/ the bound var.


@des(1)
def func():
    print "!!"
cwallenpoole
  • 79,954
  • 26
  • 128
  • 166
  • I think you want your innermost function to call `func`, not just return it. That is, replace `return func` with `return func()`. You might want to use varargs syntax (`*args, **kwargs`) to allow the decorated function to take some arguments, too. – Blckknght Aug 20 '13 at 05:21
  • @Blckknght But then one level of functions is missing. Look at my answer. – glglgl Aug 20 '13 at 05:30
  • @glglgl: I'm not sure I follow. cwallenpool's answer has the right number of nested functions, though not presented as neatly as yours (and without the `()` to make the innermost one work). You could actually condition the creation of the innermost function on `i==1` and if its not needed, just return `func` from the decorator (the second level function). – Blckknght Aug 20 '13 at 05:41
  • @Blckknght Right, but then again the decoration of the function fails instead of the calling. – glglgl Aug 20 '13 at 06:52
  • @Blckknght d'oh! Well, fixed now. – cwallenpoole Aug 20 '13 at 14:05
2

As the other answers have explained, your current issue is that you're getting the exception raised when the decorator is applied to the function, not when the function is called.

To fix this, you need to make the decorator return a function that does the exception raising. Here's how that could work:

import functools

def des(i):
    def decorator(func):
        if i != 1:
            return func # no wrapper needed

        @functools.wraps(func)
        def raiser(*args, **kwargs):
            raise Exception

        return raiser

    return decorator

The des function is a "decorator factory". It doesn't really doesn't do anything other than providing a scope to hold the i parameter for the decorator that it returns.

The decorator function does the check to see if anything special needs to be done. If not, it returns the decorated function unmodified. If i==1, it returns a custom function.

The raiser function is the decorator's return value if i==1. It always raises an exception when it is called. The functools.wraps decorator applied to it is not strictly necessary, but it makes it look more like the original function (same __name__, __doc__, etc).

BenMorel
  • 34,448
  • 50
  • 182
  • 322
Blckknght
  • 100,903
  • 11
  • 120
  • 169
  • This is a nice solution as well: the distinction between `i == 1` and `!=` happens as early as possible, but the exception happens at call time, as favourized. – glglgl Aug 20 '13 at 06:53
1

I am missing one function level here.

ITYM

import functools
def des(i): # this is the "decorator creator", called with des(1)
    def deco(func): # this is returned and is the real decorator, called at function definition time
        @functools.wraps(func) # sugar
        def new_func(*a, **k): # and this is the function called on execution.
            if i == 1:
                raise Exception # I hope this is just for testing... better create a new exception for this
            else:
                return func(*a, **k)
        return new_func
    return deco

@des(1)
def func():
    print "!!"

if __name__ == '__main__':
    try:
        func()
    except Exception:
        print 'error'
glglgl
  • 89,107
  • 13
  • 149
  • 217