简体   繁体   中英

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.

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

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

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, ie, 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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM