Python v3.x: gestionnaire d'exceptions pour les fonctions coroutine et synchrones. En général, pour tout

Pendant mon temps libre, je travaille sur mon petit projet . Écrit en Python v3.x + SQLAlchemy. Peut-être que j'écrirai un jour sur lui, mais aujourd'hui je veux parler de mon décorateur pour gérer les exceptions. Il peut être utilisé à la fois pour les fonctions et les méthodes. Synchrone et asynchrone. Vous pouvez également connecter des gestionnaires d'exceptions personnalisés.

Le décorateur ressemble actuellement à ceci:
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 

Nous analyserons dans l'ordre. __slots__ J'utilise pour économiser un peu de mémoire. C'est utile si l'objet est utilisé trop souvent.

Au stade de l'initialisation dans __init__, nous sauvegardons custom_handlers (au cas où il était nécessaire de les transférer). Au cas où, il a indiqué que nous nous attendions à y voir un dictionnaire, bien que, peut-être à l'avenir, il soit logique d'ajouter quelques vérifications approfondies. La propriété self.exclude contient une liste d'exceptions que vous n'avez pas besoin de gérer. Dans le cas d'une telle exception, la fonction avec le décorateur renverra None. Pour le moment, la liste est affinée pour mon projet, et il est peut-être judicieux de la mettre dans une configuration distincte.

La chose la plus importante se produit dans __call__. Par conséquent, lorsque vous utilisez le décorateur, vous devez l'appeler. Même sans paramètres:

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

C'est-à-dire ce sera faux et une erreur sera générée:

 @ProcessException def some_function(*args): return None 

Dans ce cas, nous obtenons la fonction courante, que, selon le degré de son asynchronie, nous traiterons soit comme un synchrone régulier, soit comme une coroutine.

À quoi vous pouvez faire attention ici. Le premier est un contrôle sur la propriété:

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

Pourquoi est-ce que je fais ça?

 Bien sûr 
          pas parce que 
                       Je suis IT Mayakovsky 
                                    et ils me paient ligne par ligne.

Deux sont là pour améliorer la lisibilité (oui, parce que le code peut être pris en charge par une personne ayant des inclinations sadiques), et nous faisons self.custom_handlers .__ get __ (self, self .__ class__) au cas où nous déciderions gestionnaires stockés dans la classe @property.

Par exemple, comme ceci:

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

Si nous ne faisons pas self.custom_handlers .__ get __ (...), alors au lieu du contenu de @property nous obtiendrons quelque chose comme <objet de propriété à 0x7f78d844f9b0>.

En fait, l'exemple ci-dessus montre comment connecter des gestionnaires personnalisés. En général, cela se fait comme suit:

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

Dans le cas d'une classe (si nous allons passer des propriétés / méthodes), nous devons tenir compte du fait qu'au stade de l'initialisation du décorateur de classe en tant que tel, les méthodes et les propriétés ne sont pas de simples fonctions. Par conséquent, nous ne pouvons transmettre que ce qui est annoncé ci-dessus. Par conséquent, l'option @property est la possibilité d'utiliser via self toutes les fonctions dont le code est inférieur. Eh bien, soit vous pouvez utiliser des lambdas si vous n'avez pas besoin de vous-même.

Pour le code asynchrone, tous les exemples ci-dessus sont vrais.

En fin de compte, je veux attirer l'attention sur le fait que si une exception ne rencontrait pas de gestionnaires personnalisés sur son chemin, elle se lève simplement davantage.

En attente de vos commentaires. Merci d'avoir prêté attention à mon article.

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


All Articles