Search code examples
pythonsingletonpython-decorators

Need clarification on how a "decorator class" work in Python


I'm learning about how to create singletons in Python (please, don't go into why singletons are bad or such, it's not the topic here).

Based on one implementation, I tried doing it like this:

class Singleton(object):
    def __init__(self, klass):
        print "S init"
        self.klass = klass
        self.instance = None
    def __call__(self, *args, **kwargs):
        print "S call"
        if self.instance is None:
            self.instance = self.klass(*args, **kwargs)
        else:
            self.instance(*args, **kwargs)
        return self.instance

@Singleton
class KlasseA:
    def __new__(cls, *args, **kwargs):
        print "KA new"
    def __init__(self, s):
        print "KA init"
        self.__init2(s)
    def __call__(self, s=None):
        print "KA call"
        self.__init2(s)
    def __init2(self, s):
        if s: self.s = s

@Singleton
class KlasseB:
    def __new__(cls, *args, **kwargs):
        print "KB new"
    def __init__(self, s):
        print "KB init"
        self.__init2(s)
    def __call__(self, s=None):
        print "KB call"
        self.__init2(s)
    def __init2(self, s):
        if s: self.s = s

Now, testing the above by appending the following lines in the same .py file:

print ""
a = KlasseA('one')
print "a -> ", id(a), a.s
b = KlasseA('two')
print "b -> ", id(b), b.s
print "a -> ", id(a), a.s
c = KlasseA()
print "c -> ", id(c), c.s
print "b -> ", id(b), b.s
print "a -> ", id(a), a.s
d = KlasseB('three')
print "d -> ", id(d), d.s
print "a -> ", id(a), a.s

I got the following:

S init
S init

S call
KA init
a ->  140525844905496 one
S call
KA call
b ->  140525844905496 two
a ->  140525844905496 two
S call
KA call
c ->  140525844905496 two
b ->  140525844905496 two
a ->  140525844905496 two
S call
KB init
d ->  140525844905568 three
a ->  140525844905496 two

So, the singleton decorator indeed works. What I don't understand here:

  1. I thought decorators redefined the call, so the call KlasseA() is effectively a call to Singleton(KlasseA)(). Shouldn't this result in a new Singleton instance for every KlasseA() invocation?

  2. I noticed that there were two "S init" lines, most likely because Singleton() was invoked during declaration of KlasseA and KlasseB. Thus, there were two instances of Singleton was created. Where were these instances being kept?

  3. Is there a possible 'gotcha' with the above Singleton decorator recipe?


Solution

  • Decorators are applied when the class is defined, not when it is instantiated. Regarding your point 1, it's really equivalent to:

    class KlasseA():
        ...
    
    KlasseA = Singleton(KlasseA)
    
    a = KlasseA()
    

    Regarding your point 2, the names KlasseA and KlasseB are bound to instances of Singleton, as shown above.

    Regarding point 3, I'm not sure it makes sense to invoke the __call__ method of the single instance of a class just because you tried to create a new instance. That is, I would think Singleton.__call__ should simply be

    def __call__(self, *args, **kwargs):
        if self.instance is None:
            self.instance = self.klass(*arg, **kwargs)
        return self.instance