Reputation: 842
I want to create a class, and all objects need to have a unique identifier key
, and If I attempt to create a new instance of the object with a previously existent key, the instance should be the same as the one that already existing.
Similar to a singleton class, but in this case instead of one class, there are many but different.
My first approach was this
class Master:
existent = {}
def __init__(self, key, value=None):
try:
self = Master.existent[key]
return
except KeyError:
Master.existent[key] = self
# Rest of the __init__ method
But when I compare two objects, something like this A = Master('A', 0)
and B = Master('B', 0)
, the B
doesn't share any attributes that It should have, and if the Master class has any _method
(single underscore), It also doesn't appear.
Any Idea how could I do this?
I think this is similar to the Factory Methods Pattern, but I'm still having trouble to find the parallels, or how to implemented in an elegant form.
EDIT:
The class basically has two proprieties and that's it, but many things would Inherit and/or contain instances of this as type, the easy way I thought I could do it, was extracting the properties from the existing instance corresponding to said key
, assigning them to the new instance and abuse from the fact that they will have same hash
output and the the equal
operator will behave according to hashes so I can use ==
and is
operators with no problem.
This Idea solves my problem, but overall I think this could be a common or interesting enough scenario to tackle.
Upvotes: 0
Views: 711
Reputation: 2419
You can use the __new__
method to handle this. You don't want to call __init__
unless you want to create a new object with a new key, and __new__
can be used to first check if the key is unique before calling __init__
.
class Master(object):
instances = {}
def __new__(cls, key, value=None):
if key in Master.instances:
return Master.instances[key]
else:
instance = super(Master, cls).__new__(cls)
Master.instances[key] = instance
return instance
def __init__(self, key, value=None):
self.value = value
Then you can create the objects with
>>> A = Master('A',0)
>>> B = Master('B',0)
>>> C = Master('A',1)
Since A
and C
have the same key, they will point to the same object and will effectively be the same instance. Since C
has the same key as A
, it updates its value.
>>> print(A.value)
1
Any new changes to A
will be seen in C
, and vice versa.
>>> A.value = 5
>>> print(C.value)
5
But changes to A
and C
will not affect B
, and changes to B
will not affect A
or C
.
Edit:
If you want to copy values but not instances, you could just store the values in the Master.instances
dictionary and check if there's already values for the key.
class Master(object):
instances = {}
def __init__(self, key, value=None):
if key in Master.instances:
self.value = Master.instances[key]
else:
self.value = value
Master.instances[key] = value
>>> A = Master('A',0)
>>> C = Master('A',1)
>>> print(C.value)
0
Upvotes: 1
Reputation: 38962
Inspired by the answer from A Kruger
, I have another solution building off the use of the __new__
method as suggested. The main difference in this answer is that there is no need to create an inner __Master
class. The __new__
method is automatically called when Master()
is invoked, and is expected to return an instance of the Master
class. In my answer, the __new__
method returns a new instance, if needed, but returns an instance from the existent
dictionary, if possible. Note that the user accesses the Master
class as usual, i.e., they just call Master('A', 0)
. This is made possible by making the Master
class extend object
.
Here is the code:
class Master(object):
existent = {}
def __init__(self, key, value=None):
self.key = key
self.value = value
if not key in Master.existent:
Master.existent[key] = self
def __new__(cls, *args, **kwargs):
key = args[0]
if key in Master.existent:
return Master.existent[key]
else:
return super(Master, cls).__new__(cls)
def __str__(self):
return('id: ' + str(id(self)) + ', key=' + str(self.key) + ', value=' + str(self.value))
A = Master('A', 0)
print('A = ' + str(A))
B = Master('A', 1)
print('\nAfter B created:')
print('B = ' + str(B))
print('A = ' + str(A))
B.value = 99
print('\nAfter B modified:')
print('B = ' + str(B))
print('A = ' + str(A))
C = Master('C', 3)
print('\nC = ' + str(C))
And here is the output:
A = id: 140023450750200, key=A, value=0
After B created:
B = id: 140023450750200, key=A, value=1
A = id: 140023450750200, key=A, value=1
After B modified:
B = id: 140023450750200, key=A, value=99
A = id: 140023450750200, key=A, value=99
C = id: 140023450750256, key=C, value=3
Note that A
and B
have the same id
(they are the same object). Also note that changes to A
or B
affect each other, since they are the same object.
Upvotes: 1
Reputation: 38962
I don't think you can do that using the __init__()
method, because a new instance of the class has already been created when that method is called. You probably need to create a factory type method something like:
class Master:
existent = {}
init_OK = False
def __init__(self, key, value=None):
if not Master.init_OK:
raise Exception('Direct call to Master() is not allowed')
Master.init_OK = False
self.key = key
self.value = value
Master.existent[key] = self
@staticmethod
def make(key, value=None):
try:
inst = Master.existent[key]
except:
Master.init_OK = True
inst = Master(key, value=value)
return inst
Upvotes: 1