6

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()
6
  • What is the rationale behind the singleton? Commented Oct 27, 2015 at 9: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. Commented Oct 27, 2015 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? Commented Oct 27, 2015 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 Commented Oct 28, 2015 at 7: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 Commented Oct 28, 2015 at 8:50

1 Answer 1

19

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
Sign up to request clarification or add additional context in comments.

5 Comments

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: 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.
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: you'd use a function: module.config(). The function then uses a global to cache.
I read the answer by @agf on 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)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.