Reputation: 4350
This singleton pattern implementation was working as expected, but there is a situation unexpected (see Test). What would have been wrong?
Code:
class Singleton(object):
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super(Singleton, cls).__new__(
cls, *args, **kwargs)
print("new class created")
return cls._instance
class CounterClassBase(object):
def __init__(self, *args, **kwargs):
super(CounterClassBase, self).__init__(*args, **kwargs)
print("Counter class init in base")
class Counter(CounterClassBase, Singleton):
counter = None
def __init__(self, *args, **kwargs):
super(Counter, self).__init__(*args, **kwargs)
print("Counter class init")
self.counter = 0
import pdb
pdb.set_trace()
Test:
(Pdb) var1 = Counter()
new class created
Counter class init in base
Counter class init
(Pdb) var2 = Counter()
Counter class init in base
Counter class init
(Pdb) var3 = Counter()
Counter class init in base
Counter class init
(Pdb) var1.counter = 3
(Pdb) var1.counter
3
(Pdb) var2.counter
3
(Pdb) var3.counter
3
(Pdb) var4 = Counter()
Counter class init in base
Counter class init
(Pdb) var4.counter
0
(Pdb)
Expected value for var4.counter
was 3, not 0.
As mentioned in the answers, my test was wrong. There is seen obviously that the "Counter class init" statement is printed, so init() is called, so variables are initialized everytime a new class instance is created.
So I changed my code as follows (source: https://stackoverflow.com/a/8665179/1952991) :
class Singleton(type):
def __init__(self, *args, **kwargs):
super(Singleton, self).__init__(*args, **kwargs)
self.__instance = None
def __call__(self, *args, **kwargs):
if self.__instance is None:
self.__instance = super(Singleton, self).__call__(*args, **kwargs)
return self.__instance
class Counter(object):
__metaclass__ = Singleton
counter = None
def __init__(self, *args, **kwargs):
print("Counter class init")
self.counter = 0
Upvotes: 0
Views: 141
Reputation: 90
The reason is that __init__
is called every time you call the constructor. You've overridden __new__
to always return the same instance, but the initialization code that sets the counter to 0 is still called every time, right after __new__
. If you check var1.counter
after you've assigned var4
, you'll find that it also returns 0.
Instead of doing it this way, consider the following:
class Counter():
counter = 0 # Initialize the field here, rather than in __init__
def __init__(self, *args, **kwargs):
super(Counter, self).__init__(*args, **kwargs)
print("Counter class init")
This will initialize the counter
field only once, and not overwrite it when a new variable is assigned using the Counter()
constructor.
Upvotes: 2
Reputation: 5036
It's zero because Counter.__init__
assigns it to zero. There's only one singleton object, as intended, but the code in both Counter.__init__
and CounterBase.__init__
is executed each time it's called. To make it work, you would have to initialise the counter attribute in the Singleton
class itself. I can see that you wanted to keep the counting and the singleton aspects separate, but for this they are a bit too separate.
Upvotes: 2
Reputation: 1961
So your test is wrong. Var 4 is correct. Even thought it is using the originally created class it sets self.counter == 0
in your Counter().__init__()
.
I would expect if you run your test again and instantiate all your counters at the same time then set the variables to 3, they will all be 3. I'm not sure is this is your desired behaviour.
Upvotes: 2