Search code examples
pythonpython-typingpyright

Python singleton pattern with type hints


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.


Solution

  • 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()