equinox icon indicating copy to clipboard operation
equinox copied to clipboard

How to make a singleton?

Open nstarman opened this issue 2 months ago • 8 comments

Before, the following used to work.

_singleton_insts: dict[type, object] = {}


class SingletonMixin:
    """Singleton class.

    This class is a mixin that can be used to create singletons.

    Examples
    --------
    >>> class MySingleton(SingletonMixin):
    ...     pass

    >>> a = MySingleton()
    >>> b = MySingleton()
    >>> a is b
    True

    """

    def __new__(cls, /, *_: Any, **__: Any) -> "Self":
        # Check if instance already exists
        if cls in _singleton_insts:
            return cast("Self", _singleton_insts[cls])
        # Create new instance and cache it
        self = object.__new__(cls)
        _singleton_insts[cls] = self
        return self

Now the _is_initializing checker calls .remove() but this errors because the singleton's __new__ bypasses the initialization process. Is there a better pattern I should be using, or should I push a PR so that .remove() doesn't error if the id hash isn't present?

nstarman avatar Oct 21 '25 19:10 nstarman

This is Hyrum's law at its finest 😄

So I wonder if the correct choice here would be for you to override the metaclass instead? As it is you'll still be paying the overhead of __check_init__, and/or subject to whatever else type(Module).__call__ may end up with over time. (Although realistically it's probably stable now.)

If that doesn't work for any reason then I agree your solution sounds reasonable :)

patrick-kidger avatar Oct 21 '25 19:10 patrick-kidger

Any chance you'd accept a PR for ModuleMeta to implement a singleton class argument?

class MyModule(Module, singleton=True): ...

nstarman avatar Oct 23 '25 16:10 nstarman

I think probably not I'm afraid – that this niche enough that it's better for users to provide their own implementation. :)

patrick-kidger avatar Oct 24 '25 21:10 patrick-kidger

Fair. Then I'll probably make a micro-library called Oncequinox that vendors just the metaclass subclass. The ~subclass~ metaclass is private. Any way it can be made public? Maybe in internal?

nstarman avatar Oct 24 '25 21:10 nstarman

Guesing you mean the metaclass - in this case then type(eqx.Module) might be what you're after?

patrick-kidger avatar Oct 24 '25 22:10 patrick-kidger

I suppose I could try ModuleMeta: type[type[eqx.Module]] = type(eqx.Module) to try to make static checkers happy, but in general type(eqx.Module) will not.

nstarman avatar Oct 25 '25 20:10 nstarman

https://pypi.org/project/oncequinox/

nstarman avatar Oct 27 '25 00:10 nstarman

https://github.com/patrick-kidger/equinox/pull/1135

nstarman avatar Oct 27 '25 00:10 nstarman