I have been trying to use the Singleton pattern in Python with proper type hints.
Here is my attempt
from typing import Any, TypeVar, override
T = TypeVar('T', bound='SingletonBase')
class Singleton(type):
_instances: dict[type[T], T] = {} # type variable T has no meaning in this context
@override
def __call__(cls: type[T], *args: Any, **kwargs: dict[str, Any]) -> T:
if cls not in cls._instances: # Access to generic instance variable through class is ambiguous
instance = super().__call__(*args, **kwargs)
cls._instances[cls] = instance # Access to generic instance variable through class is ambiguous
return cls._instances[cls] # Expression of type T is incompatible with return type T@__call__
class SingletonBase(metaclass=Singleton):
pass
I get a lot of complaints from the type checker. (I have annotate my code with the complaints as comments)
I found this answer Type hinting for generic singleton?
But that only handles annotations for the return type. I would like to understand what's going on here and learn how to implement the Singleton pattern with proper type hints.
Here is the most reasonable way to define a "singleton" pattern in Python:
class _Foo:
_instance: "_Foo" = None
def Foo() -> _Foo:
if _Foo._instance is None:
_Foo._instance = _Foo()
return _Foo._instance
The above plays nice with type-annotations, doesn't' require a metaclass, and gives you the exact same guarantee that someone won't instantiate multiple instances of your class (which is no guarantee, since even with your metaclass example, someone can define multiple instances of your class).
Or as pointed out in the comments, even more simply,
import functools
class _Foo:
pass
@functools.cache
def Foo() -> _Foo:
return _Foo()