I'm trying to create a little service locator. All services are children classes of BaseService class. I use registerService method to register a service class type with its instance and it is stored in the _services Dict. Then you can get the service instance calling getService with the class type needed. Example:
ServiceLocator.instance().registerService(LogService,LogService())
logServiceInstance =ServiceLocator.instance().getService(LogService)
This is the code showing the error:
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]
I get error: Invalid type "servicelocator.E" at the line where the Dict annotation is added as I show in the code.
I'm using Python 3.6.4 and MyPy 0.590. Mypy flags are:
--ignore-missing-imports --strict
Shouldn't this work correclty?
No. Neither the __init__
function nor your class is generic, so the E
type has no meaning within the context of the __init__
function.
The way you'd make this typecheck is to have your ServiceLocator class be generic:
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]
That said, this would likely not do what you'd expect: you'd be insisting that your ServiceLocator is capable of storing exactly one type of service, and no other.
What you want instead is a way of establishing some invariant on your _services
field: to state that there's some relationship that must always be true of every key-value pair.
However, AFAIK, this is impossible to do using Python's type system: dicts (and other containers) are treated in a completely homogenous way. We know the keys must be a particular type and the values must be some other type, but that's about it.
You'll have to settle instead for checking this relationship at runtime:
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
Note the assert in the last method -- mypy is smart enough to understand basic assert and isinstance checks. So before the assert, instance
is of type BaseService
; after the assert mypy understands to infer that the narrowed type is E
.
(You could also use a cast, but I personally prefer using explicit runtime checks in cases where the typesystem isn't strong enough to express some constraint.)