字典类型不适用于使用 mypy 的 TypeVar?

Dict type does not work with TypeVar using mypy?

我正在尝试创建一个小服务定位器。所有服务都是 BaseService class 的子 classes。我使用 registerService 方法在其实例中注册服务 class 类型,并将其存储在 _services Dict 中。然后你可以得到调用 getService 的服务实例,需要 class 类型。示例:

ServiceLocator.instance().registerService(LogService,LogService())
logServiceInstance =ServiceLocator.instance().getService(LogService)

这是显示错误的代码:

  E = TypeVar('E', bound=BaseService)


  class ServiceLocator(QObject): #type: ignore

      _instance = None;
      allServicesInited = pyqtSignal()

      def __init__(self) -> None:
          super().__init__()
          self._instance = self

          self._services: Dict[Type[E], E] = {} # This gives error: Invalid type "servicelocator.E"

      def registerService(self, t: Type[E], instance: E)->None:
          self._services[t]=instance
      def getService(self,service: Type[E])-> E:
          return self._services[service]

我在添加 Dict 注释的行收到错误:无效类型 "servicelocator.E",如我在代码中所示。

我正在使用 Python 3.6.4 和 MyPy 0.590。 Mypy 标志是:

--ignore-missing-imports --strict

这不应该正确吗?

没有。 __init__ 函数和您的 class 都不是通用的,因此 E 类型在 __init__ 函数的上下文中没有任何意义。

进行此类型检查的方法是让您的 ServiceLocator class 通用:

from typing import Generic

# ...snip...

class ServiceLocator(QObject, Generic[E]): #type: ignore
    _instance = None;
    allServicesInited = pyqtSignal()

    def __init__(self) -> None:
        super().__init__()
        self._instance = self

        self._services: Dict[Type[E], E] = {}

    def registerService(self, t: Type[E], instance: E) -> None:
        self._services[t] = instance

    def getService(self, service: Type[E]) -> E:
        return self._services[service]

也就是说,这可能不会达到您的预期:您会坚持认为您的 ServiceLocator 只能存储一种类型的服务,而不能存储其他类型的服务。

您想要的是一种在 _services 字段上建立一些不变量的方法:声明每个键值对都必须始终存在某种关系。

但是,据我所知,使用 Python 的类型系统是不可能做到的:字典(和其他容器)以完全同质的方式处理。我们知道键必须是特定类型而值必须是其他类型,但仅此而已。

您将不得不在运行时检查此关系:

class ServiceLocator(QObject): #type: ignore
    _instance = None;
    allServicesInited = pyqtSignal()

    def __init__(self) -> None:
        super().__init__()
        self._instance = self

        self._services: Dict[Type[BaseService], BaseService] = {}

    def registerService(self, t: Type[E], instance: E) -> None:
        self._services[t] = instance

    def getService(self, service: Type[E]) -> E:
        instance = self._services[service]
        assert isinstance(instance, service)
        return instance

注意最后一个方法中的 assert -- mypy 足够聪明,可以理解基本的 assert 和 isinstance 检查。所以在assert之前,instance的类型是BaseService;在 assert mypy 理解推断缩小的类型是 E.

之后

(您也可以使用强制转换,但我个人更喜欢在类型系统不够强大以表达某些约束的情况下使用显式运行时检查。)