Правильно добавьте аннотации типов в контейнер сервисов.

Вопрос или проблема

Я пытаюсь добавить аннотации типов к “контейнеру сервисов” с относительно строгой типизацией, но испытываю трудности с правильным определением подсказок типов.

По сути, контейнер сервисов – это усовершенствованный словарь, где ключом должен быть тип ABC или Протокол (т.е. “абстрактный класс”). Значение словаря – это общий ServiceEntry[T], где T – это подкласс ключа (“абстрактного класса”).

Вот моя реализация записи сервиса:

class ServiceEntry[T]:
    service_class: type[T]
    service_life: ServiceLife
    instance: T | None

    def __init__(self, service_class: type[T], service_life: ServiceLife):
        self.service_class = service_class
        self.service_life = service_life
        self.instance = None
        self.lock = threading.Lock()

А вот упрощенная реализация контейнера сервисов:

class ServiceContainer(IServiceContainer):
    def __init__(self):
        ...
        self.__services: dict[type[Any], ServiceEntry[Any]] = {}

    @override
    def register[T](
        self,
        abstract_class: type[T],
        service_class: type[T],
        service_life: ServiceLife = ServiceLife.TRANSIENT,
    ):
        assert issubclass(
            service_class, abstract_class
        ), f"'{service_class.__name__}' не реализует '{abstract_class.__name__}'"

        service = ServiceEntry[T](service_class, service_life)
        self._set_service(abstract_class, service)

    @override
    def get[T: ABC, Protocol](self, abstract_class: type[T], **overrides: Any) -> T: 

        try:
            service = self._get_service(abstract_class)
            ...
            with service.lock:
                ...
                instance = self._create_instance(service, overrides)
                ...
                return instance
        ...

Я хотел бы иметь возможность удовлетворять проверки типов, чтобы, когда я делаю что-то вроде нижеследующего, система могла выводить тип выходного сервиса и идеальным образом обнаруживать недопустимую регистрацию (т.е. service_class не является подклассом abstract_class).

class IFoo(ABC):
    ...

class Foo(IFoo):
    pass

@runtime_checkable
class IBar(Prototype):
    ...

class Bar(IBar):
    pass

services = ServiceContainer()

services.register(IFoo, Foo)
services.register(IBar, Bar)

foo_service = services.get(IFoo)
bar_service = services.get(IBar)

Может быть, я слишком амбициозен в отношении статической типизации?

Ответ или решение

Добавление аннотаций типов в контейнер обслуживания является важной задачей, которая помогает обеспечить строгую проверку типов и улучшить читаемость кода. В данной статье мы рассмотрим, как правильно аннотировать типы в вашем контейнере обслуживания, обеспечивая четкость и корректность использования.

Понимание структуры контейнера обслуживания

Контейнер обслуживания — это, по сути, специализированный словарь, где ключом является абстрактный класс или протокол, а значением — объект ServiceEntry, содержащий информацию о зарегистрированном сервисе. Основные компоненты, которые необходимо правильно аннотировать, включают:

  1. Ключ: должен быть типом, производным от ABC или Протокола.
  2. Значение: должно быть экземпляром ServiceEntry, где параметр типа — это подкласс ключа.

Реализация аннотаций типов

Давайте начнем с доработки вашего класса ServiceEntry и контейнера ServiceContainer, добавив аннотации типов для лучшего понимания и проверки типов.

Реализация ServiceEntry

Класс ServiceEntry уже имеет базовые аннотации типов, но мы можем уточнить их:

from typing import TypeVar, Generic, Optional
import threading

T = TypeVar('T')

class ServiceEntry(Generic[T]):
    service_class: type[T]
    service_life: ServiceLife
    instance: Optional[T]

    def __init__(self, service_class: type[T], service_life: ServiceLife):
        self.service_class = service_class
        self.service_life = service_life
        self.instance = None
        self.lock = threading.Lock()

Реализация ServiceContainer

Теперь переходим к классу ServiceContainer. Мы будем использовать обобщенные типы для создания аннотации как для методов регистрации, так и для получения сервисов.

from typing import Dict, Any, Type, TypeVar, overload

T = TypeVar('T')

class ServiceContainer(IServiceContainer):
    def __init__(self):
        self.__services: Dict[Type[Any], ServiceEntry[Any]] = {}

    @overload
    def register(self, abstract_class: Type[T], service_class: Type[T], service_life: ServiceLife = ServiceLife.TRANSIENT):
        ...

    def register(self, abstract_class: Type[T], service_class: Type[T], service_life: ServiceLife = ServiceLife.TRANSIENT):
        assert issubclass(service_class, abstract_class), f"'{service_class.__name__}' не реализует '{abstract_class.__name__}'"

        service = ServiceEntry[T](service_class, service_life)
        self._set_service(abstract_class, service)

    @overload
    def get(self, abstract_class: Type[T], **overrides: Any) -> T:
        ...

    def get(self, abstract_class: Type[T], **overrides: Any) -> T:
        service = self._get_service(abstract_class)

        with service.lock:
            instance = self._create_instance(service, overrides)
            return instance

Пример использования

Теперь, когда мы добавили аннотации типов, давайте рассмотрим пример использования контейнера обслуживания:

class IFoo(ABC):
    ...

class Foo(IFoo):
    pass

@runtime_checkable
class IBar(Protocol):
    ...

class Bar(IBar):
    pass

services = ServiceContainer()

services.register(IFoo, Foo)
services.register(IBar, Bar)

foo_service: IFoo = services.get(IFoo)
bar_service: IBar = services.get(IBar)

Заключение

Правильное добавление аннотаций типов в контейнер обслуживания не только улучшает строгость типов, но и упрощает диагностику ошибок при компиляции. Высокая степень аннотации позволяет компилятору и разработчикам точно понимать, какие сервисы могут быть зарегистрированы и как далее с ними взаимодействовать. Это облегчает разработку и техническое обслуживание приложений, а также повышает качество кода.

Используйте данные рекомендации, чтобы сделать ваш код более надежным и удобным для дальнейшего развития.

Оцените материал
Добавить комментарий

Капча загружается...