Python: исключение catch внутри класса

Можно ли написать обработчик исключений, чтобы уловить ошибки во время выполнения, сгенерированные всеми методами класса? Я могу сделать это, окружив каждого из них try/except:

class MyError(Exception):
    def __init__(self, obj, method):
        print 'Debug info:', repr(obj.data), method.__name__
        raise

class MyClass:
    def __init__(self, data):
        self.data = data

    def f1(self):
        try:
            all method code here, maybe failing at run time
        except:
            raise MyError(self, self.f1)

Интересно, существует ли более общий способ добиться того же - для любой ошибки, возникающей в любом месте класса. Я хотел бы иметь доступ к данным класса для печати некоторой информации об отладке. Кроме того, как мне получить имя метода отказа (f1 в примере)?

Обновление: благодаря всем, что касается безупречных ответов, идея декоратора выглядит как способ. О риске захвата ВСЕХ исключений: инструкция raise в ветке except должна повторно поднимать исключение, не теряя никакой информации, не так ли? Вот почему я положил его и в MyError...

Ответ 1

Предупреждение: если вы хотите что-то вроде этого, скорее всего, вы этого не сделаете... но если вы действительно хотите...

Что-то вроде:

import functools

def catch_exception(f):
    @functools.wraps(f)
    def func(*args, **kwargs):
        try:
            return f(*args, **kwargs)
        except Exception as e:
            print 'Caught an exception in', f.__name__
    return func

class Test(object):
    def __init__(self, val):
        self.val = val

    @catch_exception
    def calc():
        return self.val / 0

t = Test(3)
t.calc()

показывает, как украсить отдельные функции. Затем вы можете создать декоратор класса для применения этого декоратора к каждому методу (будьте осторожны classmethod's/staticmethod's/properties и т.д.)

Ответ 2

Предполагая, что у вас есть декоратор catch_exception, как в ответе @Jon Clement...

class ErrorCatcher(type):
    def __new__(cls, name, bases, dct):
        for m in dct:
            if hasattr(dct[m], '__call__'):
                dct[m] = catch_exception(dct[m])
        return type.__new__(cls, name, bases, dct)

class Test(object):
    __metaclass__ = ErrorCatcher

    def __init__(self, val):
        self.val = val

    def calc(self):
        return self.val / 0

Метакласс применяет catch_exception ко всему, что кажется методом, пока он определяет Test.


В ответ на комментарий, касающийся пользовательских сообщений для каждого метода, в качестве атрибута можно добавить такое сообщение (или даже функцию обратного вызова для генерации сообщения):

class Test(object):
    __metaclass__ = ErrorCatcher
    def __init__(self, val):
        self.val = val

    def calc(self):
        return self.val / 0

    calc.msg = "Dividing by 0 is ill-advised"

Декоратор catch_exception будет искать атрибут msg в своем аргументе и использовать его, если он найден, при обработке исключения.

Этот подход может быть расширен; вместо строки msg может быть отображением типов исключений для строк. В любом случае, строка может быть заменена (с поддержкой от catch_exception, конечно) с помощью произвольной функции обратного вызова, которая принимает, скажем, повышенное исключение в качестве аргумента.

def calc_handler(exc):
    # ...

calc.callback = calc_handler

Ответ 3

Декоратор будет хорошим решением здесь.

Вот пример того, как вы могли это сделать:

import inspect

def catch_exception_decorator(function):
   def decorated_function:
      try:
         function()
      except:
         raise MyError(self.__class__, inspect.stack()[1][3])
   return decorated_function

class MyClass(object):
    def __init__(self):
         ...

    @catch_exception_decorator
    def f1(self):
         ...

@catch_exception_decorator поверх функции является ярлыком для f1 = catch_exception_decorator(f1).

Вместо того, чтобы делать self. class, вы также можете получить доступ к данным класса из экземпляра, если вы не затеняете переменные. inspect.stack() [1] [3] - имя функции текущей функции. Вы можете использовать их для создания атрибутов исключений.