Search code examples
pythonsingletonabstract-classmetaclassabc

Implementing Singleton as metaclass, but for abstract classes


I have an abstract class and I would like to implement Singleton pattern for all classes that inherit from my abstract class. I know that my code won't work because there will be metaclass attribute conflict. Any ideas how to solve this?

from abc import ABCMeta, abstractmethod, abstractproperty

class Singleton(type):
    _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 GenericLogger(object):
    __metaclass__ = ABCMeta

    @abstractproperty
    def SearchLink(self): pass

class Logger(GenericLogger):
    __metaclass__ = Singleton

    @property
    def SearchLink(self): return ''

a = Logger()

Solution

  • Create a subclass of ABCMeta:

    class SingletonABCMeta(ABCMeta):
        _instances = {}
        def __call__(cls, *args, **kwargs):
            if cls not in cls._instances:
                cls._instances[cls] = super(SingletonABCMeta, cls).__call__(*args, **kwargs)
            return cls._instances[cls]
    
    
    class GenericLogger(object):
        __metaclass__ = SingletonABCMeta
    
        @abstractproperty
        def SearchLink(self): pass
    
    
    class Logger(GenericLogger):  
        @property
        def SearchLink(self): return ''
    

    Metaclasses work just like regular classes; you can still create subclasses and extend their functionality. ABCMeta doesn't itself define a __call__ method, so it is safe to add one.

    Demo:

    >>> from abc import ABCMeta, abstractproperty
    >>> class SingletonABCMeta(ABCMeta):
    ...     _instances = {}
    ...     def __call__(cls, *args, **kwargs):
    ...         if cls not in cls._instances:
    ...             cls._instances[cls] = super(SingletonABCMeta, cls).__call__(*args, **kwargs)
    ...         return cls._instances[cls]
    ...
    >>> class GenericLogger(object):
    ...     __metaclass__ = SingletonABCMeta
    ...     @abstractproperty
    ...     def SearchLink(self): pass
    ...
    >>> class Logger(GenericLogger):
    ...     @property
    ...     def SearchLink(self): return ''
    ...
    >>> Logger()
    <__main__.Logger object at 0x1012ace90>
    >>> Logger()
    <__main__.Logger object at 0x1012ace90>
    >>> class IncompleteLogger(GenericLogger):
    ...     pass
    ...
    >>> IncompleteLogger()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 5, in __call__
    TypeError: Can't instantiate abstract class IncompleteLogger with abstract methods SearchLink