Python v3.x: controlador de excepciones para funciones de rutina y síncronas. En general, para todo

En mi tiempo libre trabajo en mi pequeño proyecto . Escrito en Python v3.x + SQLAlchemy. Tal vez escribiré sobre eso algún día, pero hoy quiero hablar sobre mi decorador para manejar excepciones. Se puede usar tanto para funciones como para métodos. Sincrónico y asincrónico. También puede conectar manejadores de excepciones personalizados.

El decorador actualmente se ve así:
import asyncio from asyncio import QueueEmpty, QueueFull from concurrent.futures import TimeoutError class ProcessException(object): __slots__ = ('func', 'custom_handlers', 'exclude') def __init__(self, custom_handlers=None): self.func = None self.custom_handlers: dict = custom_handlers self.exclude = [QueueEmpty, QueueFull, TimeoutError] def __call__(self, func, *a): self.func = func def wrapper(*args, **kwargs): if self.custom_handlers: if isinstance(self.custom_handlers, property): self.custom_handlers = self.custom_handlers.__get__(self, self.__class__) if asyncio.iscoroutinefunction(self.func): return self._coroutine_exception_handler(*args, **kwargs) else: return self._sync_exception_handler(*args, **kwargs) return wrapper async def _coroutine_exception_handler(self, *args, **kwargs): try: return await self.func(*args, **kwargs) except Exception as e: if self.custom_handlers and e.__class__ in self.custom_handlers: return self.custom_handlers[e.__class__]() if e.__class__ not in self.exclude: raise e def _sync_exception_handler(self, *args, **kwargs): try: return self.func(*args, **kwargs) except Exception as e: if self.custom_handlers and e.__class__ in self.custom_handlers: return self.custom_handlers[e.__class__]() if e.__class__ not in self.exclude: raise e 

Analizaremos en orden. __slots__ Yo uso para ahorrar un poco de memoria. Es útil si el objeto se usa tan a menudo.

En la etapa de inicialización en __init__, guardamos custom_handlers (en caso de que fuera necesario transferirlos). Por si acaso, indicó que esperamos ver un diccionario allí, aunque, quizás en el futuro, tenga sentido agregar un par de comprobaciones. La propiedad self.exclude contiene una lista de excepciones que no necesita manejar. En caso de tal excepción, la función con el decorador devolverá Ninguno. Por el momento, la lista se agudiza para mi proyecto, y tal vez tenga sentido ponerla en una configuración separada.

Lo más importante sucede en __call__. Por lo tanto, cuando use el decorador, debe llamarlo. Incluso sin parámetros:

 @ProcessException() def some_function(*args): return None 

Es decir esto estará mal y se generará un error:

 @ProcessException def some_function(*args): return None 

En este caso, obtenemos la función actual, que, dependiendo del grado de su asincronía, procesaremos como una sincrónica regular o como una rutina.

A qué puedes prestar atención aquí. El primero es un control de la propiedad:

 if self.custom_handlers: if isinstance(self.custom_handlers, property): self.custom_handlers = self.custom_handlers.__get__(self, self.__class__) 

¿Por qué estoy haciendo esto?

 Por supuesto 
          no porque 
                       Soy IT Mayakovsky 
                                    y me pagan línea por línea.

Dos si están aquí para mejorar la legibilidad (sí, porque el código puede ser apoyado por una persona con inclinaciones sádicas), y hacemos self.custom_handlers .__ get __ (self, self .__ class__) en caso de que decidamos manejadores almacenados en la clase @property.

Por ejemplo, así:

 class Math(object): @property def exception_handlers(self): return { ZeroDivisionError: lambda: '   ,   ' } @ProcessException(exception_handlers) def divide(self, a, b): return a // b 

Si no hacemos self.custom_handlers .__ get __ (...), en lugar del contenido de @property obtendremos algo como <objeto de propiedad en 0x7f78d844f9b0>.

En realidad, el ejemplo anterior muestra cómo conectar controladores personalizados. En general, esto se hace de la siguiente manera:

 @ProcessException({ZeroDivisionError: lambda: '   ,   '}) def divide(a, b): return a // b 

En el caso de una clase (si vamos a pasar propiedades / métodos), debemos tener en cuenta que en la etapa de inicialización del decorador de clases, no existen tales métodos y las propiedades / métodos son funciones simples. Por lo tanto, solo podemos transmitir lo anunciado anteriormente. Por lo tanto, la opción @property es la capacidad de usar a través de uno mismo todas las funciones que tienen un código más bajo. Bueno, puedes usar lambdas si self no es necesario.

Para el código asincrónico, todos los ejemplos anteriores son verdaderos.

Al final, quiero llamar la atención sobre el hecho de que si una excepción no cumple con los controladores personalizados en su camino, simplemente aumenta aún más.

Esperando sus comentarios. Gracias por prestar atención a mi artículo.

Source: https://habr.com/ru/post/473596/


All Articles