1

I am trying to create a class that stores all created instances in the class itself by using new and init magical methods together and I want to return an existing class if it was created before.

My current code looks like this:

class Obj():
_list = []

def __new__(cls, id, *args, **kwargs):
    print('new')
    for o in Obj._list:
        if o.id == id:
            print('existent')
            return            # 'return o' DOES NOT WORK!
    instance = super(Obj, cls).__new__(cls, *args, **kwargs)
    return instance

def __init__(self, id, *args, **kwargs):
    print('init')
    super().__init__(*args, **kwargs)
    self.id = id
    Obj._list.append(self)

Obj(1)
Obj(1)

The code works as it does not produce doubles in the Obj._list (second call of Obj(1) prints 'existent' and cancels the creation of another instance). But when I change the line return in the new-function to return o then two instances are created though I want a pointer / the name of the existent instance to be returned and not the creation of another instance.

Any chance to achieve this behaviour with this code or any other solution? I am trying to avoid a function inside the class like:

def get(id):
    for i in Obj._list:
        if i.id == id:
            return i

Obj.get(1).my_method_call()

Any help is appreciated! Many thanks in advance!

Ulrich

Ulrich
  • 249
  • 4
  • 10
  • Please check my answer to a similar question: https://stackoverflow.com/a/58287944/4349415 – Mike Scotty Jan 18 '21 at 21:21
  • Hi Mike, this looks like pointing in the right direction. But in your example Foo() can be called without the ID as an argument. Good on this is that you can stay with one instance, but for my needs it would be better to call the class always with the ID as argument. And how to list/access instances that are not assigned to variables? – Ulrich Jan 18 '21 at 21:30
  • If `.__new__()` returns an instance of the class that it was invoked on, then `.__init__()` will be called on that instance - even if it was an already-existing instance. In your case, this is reinitializing the instance, and adding it to your list again. You don't really want both methods here - remove `__init__`, and put its actual initialization statements at the bottom of `__new__`. – jasonharper Jan 18 '21 at 21:47
  • Hi Jason, would that enable the return statement delivering an existing instance back? – Ulrich Jan 18 '21 at 21:49

2 Answers2

1

Here's a modified version of an answer I gave to a similar question:

def singleton_id(cls):
    instances={}
    def getinstance(id, *args, **kwargs):
        key = "{}__{}".format(cls, id)
        if key not in instances:
            instances[key] = cls(id, *args, **kwargs)
        return instances[key]
    return getinstance

@singleton_id
class Obj():
    def __init__(self, id, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.id = id

print(id(Obj(1)) == id(Obj(1))) # True
print(id(Obj(1)) == id(Obj(2))) # False

As you can see by the output of the print statements, the objects created with the same ID are identical.


Since you want to access the already created instances, I've modified the answer even more.

Note: I've removed the cls from the key, so this will no longer work as a decorator for different classes with the same key, but this seems to be no requirement in your case.

def singleton_id(cls):
    def getinstance(id, *args, **kwargs):
        if id not in singleton_id.instances:
            singleton_id.instances[id] = cls(id, *args, **kwargs)
        return singleton_id.instances[id]
    return getinstance
singleton_id.instances = {}

@singleton_id
class Obj():
    def __init__(self, id, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.id = id

print(id(Obj(1)) == id(Obj(1))) # True
print(id(Obj(1)) == id(Obj(2))) # False
print(singleton_id.instances[2].id) # 2
Mike Scotty
  • 10,530
  • 5
  • 38
  • 50
  • Hi Mike, calling Obj(ID) works. Last point: Any chance to list / iterate over all created instances? I have no clue how to access the instances directory in the singleton! Cheers, Ulrich – Ulrich Jan 18 '21 at 21:54
  • Thanks alot! Works perfect! The only downside is that you cannot check if an instance exists with something like: if Obj(3) == True (because any call will an unknown ID will cause a creation). Or can you find out if Obj() is used in an If-Statement? – Ulrich Jan 18 '21 at 22:17
  • @Ulrich You can check with ``if 3 in singleton_id.instances``, as ``singleton_id.instances`` is just a simple dictionary. – Mike Scotty Jan 18 '21 at 22:20
  • Yes, got it! Just thought about the thing in the comment above if you can check if Obj(X) is used in an if-statement... – Ulrich Jan 18 '21 at 22:21
0

Finally I brushed up the solution from Mike into a fully working code example (including adding methods to instances and deleting instances). So you have a fully working object handler in Python (e.g. good for games). The only downside of this you can see in the last line of code: if you have deleted an instance and you try to call it then a new, empty one is set up immediately. Here's the full solution:

def singleton_id(cls):
    singleton_id.instances = {}
    def getinstance(id, *args, **kwargs):
        if id not in singleton_id.instances:
            singleton_id.instances[id] = cls(id, *args, **kwargs)
        return singleton_id.instances[id]
    return getinstance


@singleton_id
class Obj():

    def __init__(self, id, *args, **kwargs):
        print('init')
        super().__init__(*args, **kwargs)
        self._methods = []
        self.id = id

    def delete(self):
        del singleton_id.instances[self.id]

    def addMethod(self, method, givenName = ''):
        print('add')
        if givenName == '': 
            N = method.__name__
        else: 
            N = givenName
        self._methods.append(N)
        exec('self.' + N + ' = ' + method.__name__ + '.__get__(self)')

    def executeMethod(self, method):
        if type(method) != str:
            method = method.__name__
        if method in self._methods:
            exec('self.' + method + '()')

    def put(self):
        print('I am', self.id)


# testing

Obj(1)  # creation
Obj(1)  # call
Obj(1).put()
print(Obj(1))

def x(self):
    print('hello,', self.id)
Obj(1).addMethod(x)
# can be called in different ways:
Obj(1).x()
Obj(1).executeMethod(x)
Obj(1).executeMethod('x')

Obj(1).delete()

Obj(1).put() # immediately creates new instance!
Ulrich
  • 249
  • 4
  • 10