Python v3.x: manipulador de exceções para corotina e funções síncronas. Em geral, para tudo

No meu tempo livre, trabalho no meu pequeno projeto . Escrito em Python v3.x + SQLAlchemy. Talvez eu escreva sobre ele algum dia, mas hoje quero falar sobre o meu decorador para lidar com exceções. Pode ser usado para funções e métodos. Síncrona e assíncrona. Você também pode conectar manipuladores de exceção personalizados.

Atualmente, o decorador fica assim:
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 

Vamos analisar em ordem. __slots__ Eu uso para economizar um pouco de memória. É útil se o objeto for usado com tanta frequência.

No estágio de inicialização em __init__, salvamos custom_handlers (caso seja necessário transferi-los). Por precaução, ele indicou que esperamos ver um dicionário lá, embora, talvez no futuro, faça sentido adicionar algumas verificações. A propriedade self.exclude contém uma lista de exceções que você não precisa manipular. No caso de uma exceção, a função com o decorador retornará Nenhum. No momento, a lista é afiada para o meu projeto, e talvez faça sentido colocá-la em uma configuração separada.

A coisa mais importante acontece em __call__. Portanto, ao usar o decorador, é necessário chamá-lo. Mesmo sem parâmetros:

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

I.e. isso estará errado e será gerado um erro:

 @ProcessException def some_function(*args): return None 

Nesse caso, obtemos a função atual, que, dependendo do grau de assincronia, processaremos como uma síncrona regular ou como uma rotina.

O que você pode prestar atenção aqui. O primeiro é uma verificação na propriedade:

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

Por que estou fazendo isso?

 Claro 
          não porque 
                       Eu sou Mayakovsky 
                                    e eles me pagam linha por linha.

Dois se estão aqui para melhorar a legibilidade (sim, porque o código pode ser suportado por uma pessoa com inclinações sádicas), e nós fazemos self.custom_handlers .__ get __ (self, self .__ class__) caso decidamos manipuladores armazenados na classe @property.

Por exemplo, assim:

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

Se não fizermos self.custom_handlers .__ get __ (...), em vez do conteúdo de @property, obteremos algo como <objeto de propriedade em 0x7f78d844f9b0>.

Na verdade, o exemplo acima mostra como conectar manipuladores personalizados. Em geral, isso é feito da seguinte maneira:

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

No caso de uma classe (se vamos passar propriedades / métodos), precisamos levar em consideração que, na fase de inicialização do decorador da classe, não existem métodos e os métodos / propriedades são funções simples. Portanto, só podemos transmitir o que foi anunciado acima. Portanto, a opção @property é a capacidade de usar por meio de si todas as funções com menor código. Bem, você também pode usar lambdas se não for necessário.

Para código assíncrono, todos os exemplos acima são verdadeiros.

No final, quero chamar a atenção para o fato de que, se uma exceção não atender aos manipuladores personalizados em seu caminho, ela simplesmente aumentará ainda mais.

Aguardando seus comentários. Obrigado por prestar atenção ao meu artigo.

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


All Articles