Как поймать исключение в декораторе

У меня есть функция, вызывающая исключение, и я хочу, чтобы она была декоратором. Код выглядит следующим образом:

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'

но вывод:

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

Итак, как я могу поймать это исключение?


person LiGa    schedule 20.08.2013    source источник
comment
Я считаю, что это может иметь отношение к вашей проблеме: перехватывать исключение в декораторе, позволяя вызывающей стороне перехватывать исключение   -  person user2412092    schedule 20.08.2013
comment
это работает, и я понимаю, как это работает. Спасибо:)   -  person LiGa    schedule 20.08.2013


Ответы (4)


Как объяснялось в других ответах, ваша текущая проблема заключается в том, что вы получаете исключение, когда декоратор применяется к функции, а не при вызове функции.

Чтобы исправить это, вам нужно заставить декоратор возвращать функцию, которая вызывает исключение. Вот как это может работать:

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

Функция des — это «фабрика декораторов». На самом деле он не делает ничего, кроме предоставления области для хранения параметра i для декоратора, который он возвращает.

Функция decorator проверяет, не нужно ли сделать что-то особенное. Если нет, он возвращает декорированную функцию без изменений. Если i==1, он возвращает пользовательскую функцию.

Функция raiser — это возвращаемое декоратором значение, если i==1. Он всегда вызывает исключение при вызове. Применяемый к ней декоратор functools.wraps не является строго обязательным, но делает его более похожим на исходную функцию (те же __name__, __doc__ и т. д.).

person Blckknght    schedule 20.08.2013
comment
Это также хорошее решение: различие между i == 1 и != происходит как можно раньше, но исключение происходит во время вызова, как и было благоприятным. - person glglgl; 20.08.2013

Исключение возникает, когда вы определяете функцию. Единственный способ поймать это исключение:

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

Если это сбивает с толку, почему это вызывает исключение, помните, что ваш код эквивалентен:

def func():
    print '!!'
func = des(1)(func)
# des(1) = new_func, so des(1)(func) is new_func(func)
person Chris Barker    schedule 20.08.2013
comment
но это ведет к неопределенной функции - person LiGa; 20.08.2013
comment
Да, если в процессе определения функции возникнет исключение, эта функция не будет определена. На самом деле нет другого способа обойти это, кроме как назначить другую функцию для func в операторе except. Я мог бы помочь больше, если бы знал реальный код, а не этот пример. - person Chris Barker; 20.08.2013
comment
@LiGa Да, но вы можете обнаружить это с помощью исключения и соответствующим образом обработать. - person glglgl; 20.08.2013

Итак, прямо сейчас ваш код в основном сводится к следующему:

_des = des(1)

def _func();
    print '!!'

func = _des(func)

Вы используете возвращаемое значение des в качестве декоратора, и я думаю, что это вызывает проблему.

Я думаю, вы можете захотеть еще раз вложить эту возвращаемую функцию:

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 "!!"
person cwallenpoole    schedule 20.08.2013
comment
Я думаю, вы хотите, чтобы ваша самая внутренняя функция вызывала func, а не просто возвращала ее. То есть заменить return func на return func(). Возможно, вы захотите использовать синтаксис varargs (*args, **kwargs), чтобы декорированная функция также могла принимать некоторые аргументы. - person Blckknght; 20.08.2013
comment
@Blckknght Но тогда отсутствует один уровень функций. Посмотрите на мой ответ. - person glglgl; 20.08.2013
comment
@glglgl: я не уверен, что понимаю. Ответ cwallenpool имеет правильное количество вложенных функций, хотя и не представлен так аккуратно, как ваш (и без (), чтобы заставить работать самую внутреннюю). На самом деле вы можете обусловить создание самой внутренней функции i==1 и, если она не нужна, просто вернуть func из декоратора (функция второго уровня). - person Blckknght; 20.08.2013
comment
@Blckknght Верно, но опять же вместо вызова происходит сбой оформления функции. - person glglgl; 20.08.2013
comment
@Blckknght д'о! Ну, исправлено сейчас. - person cwallenpoole; 20.08.2013

Здесь мне не хватает одного функционального уровня.

ИТИМ

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'
person glglgl    schedule 20.08.2013