I have a Python (3.8) metaclass for a singleton as seen here
I've tried to add typings like so:
from typing import Dict, Any, TypeVar, Type
_T = TypeVar("_T", bound="Singleton")
class Singleton(type):
_instances: Dict[Any, _T] = {}
def __call__(cls: Type[_T], *args: Any, **kwargs: Any) -> _T:
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
In the line:
_instances: Dict[Any, _T] = {}
MyPy warns:
Mypy: Type variable "utils.singleton._T" is unbound
I've tried different iterations of this to no avail; it's very hard for me to figure out how to type this dict.
Further, the line:
def __call__(cls: Type[_T], *args: Any, **kwargs: Any) -> _T:
Produces:
Mypy: The erased type of self "Type[golf_ml.utils.singleton.Singleton]" is not a supertype of its class "golf_ml.utils.singleton.Singleton"
How could I correctly type this?
This should work:
from __future__ import annotations
import typing as t
_T = t.TypeVar("_T")
class Singleton(type, t.Generic[_T]):
_instances: dict[Singleton[_T], _T] = {}
def __call__(cls, *args: t.Any, **kwargs: t.Any) -> _T:
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
Rough explanations:
_T = TypeVar("_T", bound="Singleton")
is not correct - Singleton
is type(type(obj))
where obj: _T = Singleton.__call__(...)
. In proper usage, the argument to bound=
can only be type(obj)
or some union typing construct, not type(type(obj)
.Type variable "_T" is unbound
indicates that you need to make Singleton
generic with respect to _T
to bind _T
.The erased type of self ...
error message is telling you that you've "erased" the type checker's inferred type* of cls
. Technically speaking, __call__
is the same on a metaclass as any other instance method - the first argument is simply the type of the owning class. In the current static typing system, however, a metaclass's instance method's first argument is not in concordance with type[...]
.*The inferred type is explicitly
Self
in the following:import typing as t Self = t.TypeVar("Self", bound="A") class A: def instancemethod(arg: Self) -> None: pass @classmethod def classmethod_(arg: type[Self]) -> None: pass
Runtime is important too, so the final sanity check is to make sure you've actually implemented a singleton using this metaclass:
class Logger(metaclass=Singleton):
pass
>>> print(Logger() is Logger())
True