Python Forum
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
singleton using metaclass
#1
i need to write a singleton so i looked up some examples. here's one of them from https://stackoverflow.com/questions/6760...on#6798042
class _Singleton(type):
    """ A metaclass that creates a Singleton base class when called. """
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(_Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class Singleton(_Singleton('SingletonMeta', (object,), {})): pass

class Logger(Singleton):
    pass
i've just read about metaclasses but i don't understand how the above example uses a metaclass. shouldn't it be? -->
class _Singleton(type):
    """ A metaclass that creates a Singleton base class when called. """
    _instances = {}
    def __call__(cls, *args, **kwargs):
        if cls not in cls._instances:
            cls._instances[cls] = super(_Singleton, cls).__call__(*args, **kwargs)
        return cls._instances[cls]

class Singleton(metaclass=_Singleton('SingletonMeta', (object,), {})): pass # notice the metaclass

class Logger(Singleton):
    pass
however i still want isinstance(inst, Singleton) to be True.
Reply
#2
(Feb-10-2018, 03:27 PM)bb8 Wrote: shouldn't it be? -->
No. The first version is correct. It says that the parent class of Singleton is an *instance* of _Singleton. It follows that all subclasses of Singleton will have the __call__() method of _Singleton (when it is called on the class, eg Logger(), not on instances of Logger)

Note that the author of this code wrote it only to ensure compatibility with python 2. A simpler version works on python 3 only:
class Singleton(metaclass=_Singleton):
    pass
Edit: I think your first version can be replaced by something simpler:
Singleton = _Singleton('Singleton', (object,), {})
Reply
#3
what is the difference between your last code snippet and declaring the class?
Reply
#4
The difference is that it works also in python 2!

The code can be improved. In the following snippet, I add some features
  • The _instances dictionary is written appart from the class, so that it cannot be accessed through subclasses of Singleton
  • Direct subclasses of Singleton are final (cannot be subclassed). This is necessary to preserve the unique instance paradigm: if one could create a Logger subclass named FooLogger, it would create two instances of Logger because a FooLogger is a Logger.
  • The base Singleton class cannot be instanciated.
_instances = {}

class _Singleton(type):
    """ A metaclass that creates a Singleton base class when called. """
    def __new__(meta, name, bases, members):
        """Creates a new _Singleton instance (a singleton type)
        
        This function allows creation of a single base class named Singleton
        and direct subclasses of Singleton. Attempts to subclass one of these
        subclasses raise a TypeError
        """
        for b in bases:
            if b is not Singleton and issubclass(b, Singleton):
                raise TypeError(('Invalid base type (as subclass of Singleton):', b))
        return  type.__new__(meta, name, bases, members)

    def __call__(cls, *args, **kwargs):
        if cls is Singleton:
            raise TypeError('The base Singleton type cannot be instanciated')
        if cls not in _instances:
            _instances[cls] = super(_Singleton, cls).__call__(*args, **kwargs)
        return _instances[cls]

Singleton = type('_',(),{}) # dummy type to allow base type creation
Singleton = _Singleton('Singleton', (object,), {})

if __name__ == '__main__':
    class A(Singleton):
        pass
    a, aa = A(), A()
    assert a is aa
Notice that now the following raises TypeError:
class B(A):
    pass
Reply
#5
thanks
wouldn't __new__() get called every time a class a tried to be instantiated?
Reply
#6
(Feb-11-2018, 06:34 PM)bb8 Wrote: wouldn't __new__() get called every time a class a tried to be instantiated?
No, __new__ is called every time an instance of the metaclass _Singleton is created. It means that it will be called every time a subclass of Singleton is defined, but not when such classes try to be instantiated. You can check this easily by adding a print statement in __new__()
Reply
#7
okay

and i get this error:
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
here's the class:
class MyAppClass(QObject, Singleton):
Reply
#8
(Feb-12-2018, 04:43 PM)bb8 Wrote: and i get this error:
It means that QObject already has a metaclass and that python cannot subclass two classes having different metaclasses unless you define the subclass with a metaclass that is a common subclass of both metaclasses. It won't work with the singletons we defined above.

The pythonic solution is to forget about the Singleton and ensure by other means that there can't be more than one instance of MyAppClass.
Reply
#9
oh got it now...

what are the other ways?

i've seen people using some list or dictionary to keep track of the instance. like that?
Reply
#10
Why do you need anything to keep track of a single instance? You only need this:
class MyApp(QObject):
    pass

myapp = MyApp()
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Prefs class: singleton or something else? PatM 7 1,213 Dec-12-2022, 11:01 PM
Last Post: PatM
  initialisation and metaclass GuiOhm 1 1,817 Mar-09-2020, 03:30 PM
Last Post: Gribouillis

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020