Вопрос или проблема
Я пытаюсь добавить аннотации типов к “контейнеру сервисов” с относительно строгой типизацией, но испытываю трудности с правильным определением подсказок типов.
По сути, контейнер сервисов – это усовершенствованный словарь, где ключом должен быть тип 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
, содержащий информацию о зарегистрированном сервисе. Основные компоненты, которые необходимо правильно аннотировать, включают:
- Ключ: должен быть типом, производным от ABC или Протокола.
- Значение: должно быть экземпляром
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)
Заключение
Правильное добавление аннотаций типов в контейнер обслуживания не только улучшает строгость типов, но и упрощает диагностику ошибок при компиляции. Высокая степень аннотации позволяет компилятору и разработчикам точно понимать, какие сервисы могут быть зарегистрированы и как далее с ними взаимодействовать. Это облегчает разработку и техническое обслуживание приложений, а также повышает качество кода.
Используйте данные рекомендации, чтобы сделать ваш код более надежным и удобным для дальнейшего развития.