【问题标题】:Dict type does not work with TypeVar using mypy?字典类型不适用于使用 mypy 的 TypeVar?
【发布时间】:2018-09-29 04:49:19
【问题描述】:

我正在尝试创建一个小型服务定位器。所有服务都是 BaseService 类的子类。我使用 registerService 方法向其实例注册服务类类型,并将其存储在 _services 字典中。然后,您可以使用所需的类类型获取调用 getService 的服务实例。示例:

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

这不应该正确吗?

【问题讨论】:

    标签: python subclass type-hinting mypy type-variables


    【解决方案1】:

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

    您进行这种类型检查的方式是让您的 ServiceLocator 类是通用的:

    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 字段上建立一些不变量的方法:声明每个键值对都必须始终存在某种关系。

    但是,AFAIK,使用 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
    

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-07-23
      • 1970-01-01
      • 2020-08-03
      • 2020-08-31
      • 2021-04-18
      • 1970-01-01
      • 1970-01-01
      • 2020-05-31
      相关资源
      最近更新 更多