A standard Singleton (and related Borg) metaclass

I think Borg is an antipattern. It is a terrible, unpythonic concept that is just going to hurt every codebase it touches by breaking basic assumptions about the way objects work, to implement something that could also be implemented in a million other ways. You have not managed to make any argument in the other direction that I think hold merit.

When I mean is that you need to get some Core Developers on your side, and I am not one of them.

Well, then this is the wrong place for you. This category, Ideas, is about discussing ideas and determining if they would fit into python. ā€œDiscussingā€ means different people having different opinions and making arguments to convince others.

When you revisit Borg (IMO, Borg is even worse conceptually than Singletons, but apparently I am a bit more alone with that), you will need to provide arguments then.

But your goal, if you want to get this implemented, must be to change that.

Or accept defeat and drop the idea - which IMO is the best course for this proposal.

Dont’ tell me about patterns, tell me how it’s useful. If you want a bunch of things that only take one space in memory… isn’t that just one object? Python gives you that for free. Just use the same object.

It can with a simple counter. That’s how a GC with a refcount works.

For Singleton, I suspect a module is already enough, as Chris said.

I’m waiting as the others some real use case for Borgs. Maybe there are some that I can’t think about. But until now it seems a solution in search of a problem.

I’m tempted anyway to give a +1 only for the name :rofl:

I am fairly aware of the meaning of a discussion, thank you very much :slight_smile:

What I had meant is that I seem to be very much alone in here. And with such
overwhelming response from the community, I don’t think it’s very useful for me
right now to try and save an idea with clearly visible holes in them.

It is hubris for me to think I know more than core developers with decades; and
discussing is very much not trying to defend an idea just for the sake of it.
I have tried to hold what I proposed up to scrutiny – and there are clearly flaws in it.

I believe there are better things to do than to try and push an idea that is deeply unpopular.

Obviously. But that is done through proper study and thought. I have learnt the lessons
and before I say anything else I will come indeed better prepared.

Not so much as a full drop. But I absolutely acknowledge the defeat.
I’m glad though, I came out knowing more. And I hope to come back with
a proper proposal.

I think this is a very valid point. And one I don’t want to just answer away with a BS example.

Tbh, I was not hoping for the community to feel this strongly against it. But since I’m
not the owner of any absolute truth, it does mean that odds are, I am in the wrong.

I will read more deeply and test. And come back with a better plan and better grounding.

Thank you for the input so far, really.
And working in a different route, if you see ways forward to reshape the idea, I’m all ears :slight_smile:

Which is kind of ironic for something that can only really be done in
Python!

FWIW I have a kind of ā€œSingletonā€ that supports equivalence classes based the constructor args. It looks something like this:

class SingletonMeta(type):
    """
    Metaclass that makes classes into singletons keyed on their ``__init__`` args

    When an instance of the class is created, if an instance already existed with the same
    arguments, the existing instance is returned.  For example::

        >>> class A(metaclass=SingletonMeta):
        ...     def __init__(self, x):
        ...         self.x = x
        ...
        >>> A(1) is A(1)
        True
        >>> A(1) is A(2)
        False

    .. warning::

        Because instances are keyed on their constructor args, those arguments must always be
        hashable.  This is not enforced, however, by the type system.

    By default the storage for a singleton is global and persists for the lifetime of the
    application.  However, if you mark the class with the ``thread_local = True`` class attribute,
    its storage is thread/context-local.
    """

    thread_local: ClassVar[bool] = False

    def __init__(self, name: str, bases: tuple[type, ...], members: dict[str, object]) -> None:
        super().__init__(name, bases, members)

        if self.thread_local:
            self._instances: InstancesContainer = ThreadlocalInstancesContainer()
        else:
            self._instances = GlobalInstancesContainer()

    def __call__(self, *args: object, **kwargs: object) -> object:
        sig = signature(self.__init__)  # type: ignore[misc]
        bound = sig.bind_partial(None, *args, **kwargs)
        bound.apply_defaults()
        key = (self, bound.args[1:], tuple(bound.kwargs.items()))

        if key in self._instances.instances:
            return self._instances.instances[key]

        inst = super().__call__(*args, **kwargs)
        self._instances.instances[key] = inst
        return inst

Of course, whether or not this is a ā€œgood ideaā€ is maybe an aesthetic preference. And of course all args and values of kwargs must be hashable (IIRC mypy didn’t enforce this though so I just left it as object for now).

I fully agree that the outlook of getting this into the stdlib is basically zero. If that is op’s goal with furthering the discussion it would save time and energy to drop it.

If you mainly want to continue the discussion and understand why so many are sceptical to the need of the patterns that may still be possible.

While very many threads here in Ideas seem to have that as their only goal and tend to take take energy explaining that I’m not convinced all of them aren’t also an attempt to, or at least open to transition to, instead getting feedback and understanding of why it may not be reasonable.

In the effort of averting the former kind (which definitely can be a problem) it’s very easy to appear very hostile towards the former if you don’t have that background in mind.

This is not a direct critique of the responses I quoted (I have very high respect for both) but rather just an observation of this and other threads.

If you give up your goal of getting the idea into the language/stdlib, then you should move your thread out of the Ideas category if you still want to continue the discussion. The category is already poorly moderated - further muddling the waters by allowing non-ideas is just going to make the problem worse.

But this is just getting dragged off-topic into the age old discussion ā€œwhat is this category forā€. The above is part of my opinion on that.

Not only, but there is always an aesthetic sense to what is the kosher way.

This is an interesting third possibility nonetheless, but it introduces an explicit check for hashability

As it is – yes. I think that any further attempt at anything like it must require a rethinking of how this is done. Maybe switching from metaclass shenanigans to a decorator-based approach.

I would very much appreciate input on this regard. I do agree with several of the counterarguments presented – and I think the usecase of the singleton is smaller than I first considered (and I’ll reconsider their usage going forward).

I still think there may be some uses in situations where class instantiation is a consequence of some file that is codegened at runtime (where I first thought of it), and it’s easier to have some Borg(ā€œfooā€) that is interchangable with MyObj(ā€œbarā€) at runtime for nested data structures where there is a lot of repetition.

If I think that is worth the addition to stdlib? I’m more cautious now.

Honestly, I’d like your opinion on the whole thing.

I’m taking this thread as an early peer-review. Serious and meaningful criticisms have been placed at the idea. It’s pointless to just re-iterate in a shallow way.

So, at the moment, it’s a discussion about the topic, not an actual proposal. I’ve moved the thread into the general section to reflect this.

Not sure if I agree with you there. I think it is useful to keep the record of the rejected ideas. I did search before posting, and if I found a clear thread like this, I wouldn’t waste time proposing something that had been clearly rejected before – but I’m not the one moderating.

Again, I’m not a moderator, so this is above what I can decide, but I do feel like rejected ideas should not be moved out. The negative space is important to understand what is and isn’t a good way forward when opening a thread :slight_smile:

And my goal here was never just pushing an idea for the sake of it. I think that that sort of position is not how things should move if quality is the objective.

It all depends on how much more discussion you want to have about it AFTER it’s no longer a viable idea. Continuing the discussion isn’t about an idea any more after that, it’s about discussion of the concept, which belongs in the general category.

Well, what I had meant is that maybe rejected ideas should have a place where they live specifically. This way, the next person with a similar idea can visit the conversation before posting.

With regard to it not being a viable idea, yes. As it is, it has been rejected and that’s what it is.

I would like your thoughts on it though. Do you see any positive argument for a more standard way to make a singleton (not necessarily a metaclass)?

We have one. It’s called a module global. Why do we need another?

I don’t think changing the mechanics or API of a singleton would alter the view that, to the extent singletons are useful, Python already provides an effective and widely accepted way to implement them.

That’s interning, not a singleton, right? Possibly useful for saving memory and for optimizing equality comparisons to identity comparisons.