Any extra reason to not use classes for singletons?

Dear colleagues,

There are cases when one need to have only a single object of a certain class. In this case, it is possible to use the class itself without creating instances. There are some explicit drawbacks with this approach, I wonder is there any implicit ones?

Here is an example with the standard approach

class Counter1:
    def __init__(self):
        self.count = 0
    def increment(self):
        self.count +=1

global_counter = Counter1()
global_counter.increment()
print(global_counter.count)

and class version is


class Counter2:
    count = 0
    @classmethod
    def increment(cls):
        cls.count +=1

Counter2.increment()
print(Counter2.count)

Drawbacks for the class version: you cant use isinstance(), dunders (such as __repr__ or __getitem__) does not work, have to write @classmethod everywhere. The benefit is that there is one less variable to name.

Assume I hate naming variable so much that I’m fine with these drawbacks. I wonder is there are less evident drawback for the class version, something that will bite me in my back if I take this approach.

2 Likes

I believe that in most instances, a function is much more “telling” than a singleton, as when you’re dealing with a class you usually expect to have multiple instances of it, which is not the kind of expectation you get from a function. And when your singleton-like function gets too complex to be encapsulated in a lone function, usually it is a good idea to split it out into a separate module instead. Take your example:

from collections.abc import Callable


def make_counter() -> Callable[[bool], int | None]:
    count = 0

    def counter_nested(show: bool = False) -> int | None:
        nonlocal count

        if not show:
            count += 1
            return
        return count

    return counter_nested

counter = make_counter()

counter()
counter()
counter(show=True)

Thanks for reply, but unfortunately its not the information I’m looking for – this is already a third solution. Also, it have the same drawbacks as the class approach: no isinstance, no dunders.

By 2pi360 via Discussions on Python.org at 19Sep2022 11:38:

There are cases when one need to have only a single object of a certain
class. In this case, it is possible to use the class itself without
creating instances. There are some explicit drawbacks with this
approach, I wonder is there any implicit ones?

To me the big one is that your object doesn’t really behave like other
objects.

What you describe falls into 2 situations in my mind:

  • you only need to make one object
  • you must only make one object

FOr the former, I’d just write a regular class and make a single
instance with a well known name:

 class C:
     ....

 THE_MAIN_C = C(.....)

and just use that from then on.

For the latter, I’d much prefer to write a conventional class but to
provide a __new__ method to return the singleton instance.

That way (a) you’re getting a regular object with all its benefits and
normal behaviour (b) you can write all the rest of the class, with
methods etc, in a normal way like your other code. In short, you don’t
need to remember that your working with something weird and special.

There’s a lot to be said for objects not having unusual properties.

Cheers,
Cameron Simpson cs@cskk.id.au

What you are describing is certainly possible, but it is unusual:

  • in many object-oriented languages, like Java, classes are not first-rate values and cannot be used like this at all;
  • even in languages like Python where classes are first-rate values and can be used like this, most people still use an instance.

(By the way, some of my comments below are not aimed at you directly, but other readers who may not be familiar with the concepts you refer to, such as singletons.)

In the OOP (object-oriented programming) world, the most common solution to your problem is called a singleton. People are divided over whether singletons are great, or an anti-pattern.

(Personally, I think that singletons like None are great, but most other uses are problematic.)

An alternative to the Singleton design (anti-)pattern is the Borg.

A lesser known alternative is to just use the class itself as the one and only value, but as you point out, it has disadvantages:

  • you have to make all the methods classmethod;
  • property will not work;
  • this design is so rare that it doesn’t have a catchy name, or even a boring name;
  • will likely confuse linters and automated code checkers;
  • many programmers are like cats, and hate and fear anything different, and this is certainly different!

I don’t understand your argument that you can’t use isinstance. You can, but your class-that-is-like-a-singleton will only compare as an instance of type.

The benefits are minor:

  • avoid the need to write Singleton boilerplate;
  • save a tiny amount of memory by avoiding creating an instance.

There is another solution: just use a module.

A module contains state (global variables) and behaviour (functions), just like a class. Like a singleton, you can only have one instance of a module at any one time time: the interpreter enforces that rule for you automatically. As an example of that, see the random module.

So that’s four solutions to the problem of having only one instance with state and behaviour:

  • use a Singleton;
  • forget about one instance, use Borg with many instances with shared state;
  • use the class object directly;
  • use a module.

Which is better is a question of what exactly you are doing, who else is reading your code, and also a matter of taste.

3 Likes

Thank you, this is exactly the information I was looking for!