Search code examples
pythondesign-patternssingletonfactory-pattern

Changing `self` for another instance of same object?


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.


Solution

  • 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.