Trochę obróbki dekoratora (bardzo luźno zainspirowane Być może monadą i liftingiem). Możesz bezpiecznie usunąć adnotacje typu Python 3.6 i użyć starszego stylu formatowania wiadomości.
fallible.py
from functools import wraps
from typing import Callable, TypeVar, Optional
import logging
A = TypeVar('A')
def fallible(*exceptions, logger=None) \
-> Callable[[Callable[..., A]], Callable[..., Optional[A]]]:
"""
:param exceptions: a list of exceptions to catch
:param logger: pass a custom logger; None means the default logger,
False disables logging altogether.
"""
def fwrap(f: Callable[..., A]) -> Callable[..., Optional[A]]:
@wraps(f)
def wrapped(*args, **kwargs):
try:
return f(*args, **kwargs)
except exceptions:
message = f'called {f} with *args={args} and **kwargs={kwargs}'
if logger:
logger.exception(message)
if logger is None:
logging.exception(message)
return None
return wrapped
return fwrap
Próbny:
In [1] from fallible import fallible
In [2]: @fallible(ArithmeticError)
...: def div(a, b):
...: return a / b
...:
...:
In [3]: div(1, 2)
Out[3]: 0.5
In [4]: res = div(1, 0)
ERROR:root:called <function div at 0x10d3c6ae8> with *args=(1, 0) and **kwargs={}
Traceback (most recent call last):
File "/Users/user/fallible.py", line 17, in wrapped
return f(*args, **kwargs)
File "<ipython-input-17-e056bd886b5c>", line 3, in div
return a / b
In [5]: repr(res)
'None'
Możesz również zmodyfikować to rozwiązanie, aby zwracało coś nieco bardziej znaczącego niż None
z except
części (lub nawet uczynić rozwiązanie ogólnym, podając tę wartość zwracaną w fallible
argumentach).
exception
Metoda po prostu wywołujeerror(message, exc_info=1)
. Jak tylko przejdzieszexc_info
do dowolnej metody rejestrowania z kontekstu wyjątku, otrzymasz traceback.