@whojoannaami

Как реализовать класс-обёртку, используя Python generics?

Я пытаюсь реализовать класс-утилиту, который бы мог оборачивать результаты выполнения моих сервисов.
Суть заключается в том, что каждый мой сервис может вернуть либо объект любого типа, либо объект, принадлежащий типу написанного мной исключения.

Работающий на 100% результат, которого мне удалось добиться, без явной аннотации внутреннего типа:
from typing import Any


class ServiceResultWrapper:
    def __init__(self, result: Any | ServiceException):
        self._result = result
        self._success = not isinstance(result, ServiceException)
   
    def handle(self) -> Any:
        if not self._success:
            raise result
        return self._result

Суть заключается в том, что сервис не вызывает исключения внутри себя, а возвращает его в обертке для дальнейшей обработки.

Однако, у этого подхода есть недостаток, заключающийся в том, что мне хотелось бы видеть тип, который возвращает сервис, непосредственно на стороне вызова.

Способ, который я применил в самом начале:
from typing import TypeVar, Generic


ResultType = TypeVar("ResultType")


class ResultWrapper(Generic[ResultType]):
    def __init__(self, result: ResultType | ServiceException):
        self._result = result
        self._success = not isinstance(result, ServiceException)
   
    def handle(self) -> ResultType:
        if not self._success:
            raise result
        return self._result

По-началу я думал, что должно сработать корректно, да только вот я не заметил следующую ошибку:
def computing_service(param: int) -> ServiceResultWrapper[int]:
    if param == 42:
        return ServiceResultWrapper(
            ServiceException("This isn't the answer for every question!")
        )
    return ServiceResultWrapper(100) # An example of a successful result.


Expected type 'ServiceResultWrapper[int]', got 'ServiceResultWrapper[ServiceException]' instead

Вполне логично, ведь аннотация результата говорит, что ожидается контейнер, содержащий int, а я возвращаю ServiceResultWrapper[ServiceException].

Текущее решение:
from typing import Generic, TypeVar

from src.services.base.service_exception import ServiceException


ResultType = TypeVar("ResultType")


class ServiceResultWrapper(Generic[ResultType]):
    def __init__(self, result: ResultType):
        self._result = result
        self._success = not isinstance(result, ServiceException)

    def handle(self) -> ResultType:
        if not self._success:
            raise self._result

        return self._result

И использование Union-аннотации
def computing_service(param: int) -> ServiceResultWrapper[int] | ServiceResultWrapper[ServiceException]:
    if param == 42:
        return ServiceResultWrapper(
            ServiceException("This isn't the answer for every question!")
        )
    return ServiceResultWrapper(100) # An example of a successful result.

Решение в принципе работает: выдает нужный тип, соответственно IDE анализирует атрибуты; mypy не ругается.

Однако запись кажется мне немного громоздкой, и в целом присутствует ощущение, будто я использую generic'и неправильно. Возможно, есть способы лучше справиться с моей задачей? "Best practice".
  • Вопрос задан
  • 135 просмотров
Пригласить эксперта
Ответы на вопрос 1
@deliro
https://github.com/rustedpy/result

p.s. непонятно, зачем в твоём варианте ты хочешь смешивать мух с котлетами. Валидным возвращаемым значением вполне может быть объект подтипа Exception, в том числе, ошибкой не обязательно должен быть Exception.Часто гораздо удобней ошибкой делать Enum. (Чувствуешь, как значение и ошибка поменялись местами и твоя проверка not isinstance(result, ServiceException) только что зафейлилась?)
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы