Search code examples
python-3.xpython-3.5memoization

Memoized objects still have their __init__() invoked?


So I am creating a memoized class, and have been observing a strange behavior.

Here's the snippet of the code:

class SomeClass(object):

    _Memoized = {}

    def __new__(cls, id: str, *args, **kwargs):
        if id not in cls._Memoized:
            print('New Instance')
            cls._Memoized[id] = super(SomeClass, cls).__new__(cls, *args, **kwargs)
        else:
            print('Existing Instance')
        return cls._Memoized[id]

    def __init__(self, id: str):
        print('Running init')
        self.id = id


def test():
    w1 = SomeClass(id='w1')
    wx = SomeClass(id='w1')
    print(id(w1), id(wx), id(w1) == id(wx))

test()

Running the above code results in:

New Instance
Running init
Existing Instance
Running init   <===-------------------???
140008534476784 140008534476784 True

My questions: During the second invocation of SomeClass(), why does it execute the __init__ constructor? Wasn't the __init__ constructor only invoked on instantiating? Is there a way to prevent the __init__ from being invoked?


Solution

  • The purpose of __new__ is to create a new instance, which is why Python calls __init__ on it. You might instead override __call__ on a metaclass to avoid creating a new instance.

    class MemoMeta(type):
        def __init__(self, name, bases, namespace):
            super().__init__(name, bases, namespace)
            self.cache = {}
        def __call__(self, id_, *args, **kwargs):
            if id_ not in self.cache:
                print('New Instance')
                self.cache[id_] = super().__call__(id_, *args, **kwargs)
            else:
                print('Existing Instance')
            return self.cache[id_]
    
    class SomeClass(metaclass=MemoMeta):
        def __init__(self, id_, *args, **kwargs):
            print('Running init')
            self.id = id_
    
    
    def test():
        w1 = SomeClass(id_='w1')
        wx = SomeClass(id_='w1')
        print(id(w1), id(wx), id(w1) == id(wx))
    
    test()