4

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()
martineau
  • 99,260
  • 22
  • 139
  • 249
Zwierzak
  • 636
  • 10
  • 27
  • What is the rationale behind the singleton? – Peter Wood Oct 27 '15 at 09:30
  • I will be creating parsers of different websites, each parser has to implement the same methods, so I want to have an abstract base. Also I need only 1 parser per website, don't need different ones since parser will never store any data. It only processes the content. – Zwierzak Oct 27 '15 at 12:04
  • Why not just a modules with functions if there is no state. Or use a class but just make one. It isn't catastrophic if there are two instances, why the need to enforce it? – Peter Wood Oct 27 '15 at 15:27
  • Well, maybe I wasn't completely honest. I forgot to add that parser stores some site specific information which takes much time to scrapp, so I think it makes sense to force the programmer to be able to initiate it only once. Plus I would like to learn how to exactly implement singleton – Zwierzak Oct 28 '15 at 07:08
  • Forcing programmers isn't a good idea unless there is a real benefit, or no other safe way. Just have a function to create a parse and a map which stores the parsers – Peter Wood Oct 28 '15 at 08:50
  • Possible duplicate of [Creating a singleton in python](http://stackoverflow.com/questions/6760685/creating-a-singleton-in-python) – Peter Wood Oct 28 '15 at 09:09

1 Answers1

12

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
Martijn Pieters
  • 889,049
  • 245
  • 3,507
  • 2,997
  • I'm planning to use this recipe, the only thing which is stopping me are the following questions: 1) Let's say if I had to re-initialize a Singleton object, for example some kind of a config singleton when the config file underneath changes. How would I be able to do that? Would it make sense to add a `de_init` method in the meta class? 2) Also how would we re-initialize a singleton which is a composition of other singletons? – zapstar Dec 26 '17 at 08:41
  • 1
    @zapstar: instances are stored in `SingletonABCMeta._instances`, so you can clear them from there. However, I'd just use module global to store config, and not use the singleton pattern here, if your config needs updating. – Martijn Pieters Dec 26 '17 at 10:18
  • If I go with the module global approach then I'll have to provide a initialize method to load the config, which could have been avoided if I were using a Singleton. – zapstar Dec 26 '17 at 11:07
  • 1
    @zapstar: you'd use a function: `module.config()`. The function then uses a global to cache. – Martijn Pieters Dec 26 '17 at 11:28
  • I read the answer by @agf on https://stackoverflow.com/a/6798042/1084879, it makes sense that singleton is better when I have a data sink rather than a data source (such as global config) – zapstar Dec 26 '17 at 15:56