Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Singleton object and the copy module in Python

I defined this singleton metaclass:

class Singleton(type):
    """Metaclass which implements the singleton pattern"""

    _instances = {}

    def __call__(self, *args, **kwargs):
        if self not in self._instances:
            self._instances[self] = super(Singleton, self).__call__(*args, **kwargs)
        return self._instances[self]

Now, I want to test whether everything works OK. Here is what I've tried:

  • I created two objects of the same class (class that has Singleton as metaclass) - their id() matches
  • I created one object and assigned it to a second variable - their id() matches
  • I imported the copy module and copied the first object with copy.copy() - their id() doesn't match now

I want to know why doesn't the copied object's id match with the original object. Since it's a Singleton, shouldn't the two objects have the same id?

like image 663
linkyndy Avatar asked Jun 28 '26 19:06

linkyndy


1 Answers

The copy module does not create a new instance; instead it creates an empty class, and reassigns __class__ on that object to specifically avoid the __new__ or __init__ methods of the original.

You'll need to provide a custom __copy__ hook to return the instance unchanged instead:

class Singleton(type):
    """Metaclass which implements the singleton pattern"""

    _instances = {}

    def __call__(self, *args, **kwargs):
        if self not in self._instances:
            self._instances[self] = super(Singleton, self).__call__(*args, **kwargs)
        return self._instances[self]

    def __copy__(cls, instance):
        return instance

copy looks up the __copy__ method directly on the class; it is instead found on the metaclass, looking up __copy__ on the class will return a bound method, and copy (expecting an unbound method) passes in the instance explicitly. This means that there will be two arguments passed in to __copy__; the first is the class (an instance of the metaclass), the second the instance of that class (self in traditional methods).

Unfortunately, __deepcopy__ is looked up on the instance instead, and the metaclass method is not found.

like image 171
Martijn Pieters Avatar answered Jul 01 '26 07:07

Martijn Pieters