Paul
Paul

Reputation: 43650

How to create a class that doesn't re-create an object with identical input parameters

I am trying to create a class that doesn't re-create an object with the same input parameters. When I try to instantiate a class with the same parameters that were used to create an already-existing object, I just want my new class to return a pointer to the already-created (expensively-created) object. This is what I have tried so far:

class myobject0(object):
# At first, I didn't realize that even already-instantiated
# objects had their __init__ called again
instances = {}
def __new__(cls,x):
    if x not in cls.instances.keys():
        cls.instances[x] = object.__new__(cls,x)
    return cls.instances[x]
def __init__(self,x):
    print 'doing something expensive'

class myobject1(object):
    # I tried to override the existing object's __init__
    # but it didnt work.
    instances = {}
    def __new__(cls,x):
        if x not in cls.instances.keys():
            cls.instances[x] = object.__new__(cls,x)
        else:
            cls.instances[x].__init__ = lambda x: None
        return cls.instances[x]
    def __init__(self,x):
        print 'doing something expensive'

class myobject2(object):
    # does what I want but is ugly
    instances = {}
    def __new__(cls,x):
        if x not in cls.instances.keys():
            cls.instances[x] = object.__new__(cls,x)
            cls.instances[x]._is_new = 1
        else:
            cls.instances[x]._is_new = 0
        return cls.instances[x]
    def __init__(self,x):
        if self._is_new:
            print 'doing something expensive'

This is my first venture into overriding __new__ and I'm convinced I'm not going about it the right way. Set me straight, please.

Upvotes: 4

Views: 2747

Answers (3)

Craig Wallace
Craig Wallace

Reputation: 91

Here is my implementation of Jerry's way, using an array as the pool

def pooled(cls):
    """
    decorator to add to a class, so that when you call YourClass() it actually returns an object from the pool
    """

    pool = []

    def get_instance(*args, **kwargs):
        try:
            instance = pool.pop()
        except IndexError:
            instance = cls(*args, **kwargs)
        returned_instance = yield instance
        pool.append(returned_instance)
        print(len(pool))
        yield

    return get_instance


@pooled
class MyClass():
    def __init__(self, num):
      self.num = num


for i in range(10):
    m_gen =MyClass(i)
    n_gen = MyClass(i + 5)
    m = next(m_gen)
    n = next(n_gen)
    print(f'm num: {m.num}')
    print(f'n num: {n.num}')
    m_gen.send(m)
    n_gen.send(n)

and then another way using metaclasses so you can inherit the functionality. This one uses weakreaf valuedicts as the pool, so the objects get garbage collected better

import weakref
  
class PooledMeta(type):
  def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    self._pool = weakref.WeakValueDictionary()

  def __call__(self, *args):
    if args in self._pool:
      print('got cached')
      return self._pool[args]
    else:
      # print(self._pool.valuerefs())
      instance = super().__call__(*args)
      self._pool[args] = instance
      return instance

class MyPooled(metaclass=PooledMeta):
  def __init__(self, num):
    print(f'crating: {num}')
    self.num = num

class MyPooledChild(MyPooled):
  def __init__(self, num):
    print(f'crating child: {num}')
    self.num = num

p = []
for i in range(10):
  m = MyPooled(i)
  n = MyPooledChild(i)
  p.extend([m,n])

Upvotes: 1

Jerry Neumann
Jerry Neumann

Reputation: 425

Here's a class decorator to make a class a multiton:

def multiton(cls):
   instances = {}
   def getinstance(id):
      if id not in instances:
         instances[id] = cls(id)
      return instances[id]  
   return getinstance

(This is a slight variant of the singleton decorator from PEP 318.)

Then, to make your class a multiton, use the decorator:

@multiton
class MyObject( object ):
   def __init__( self, arg):
      self.id = arg
      # other expensive stuff

Now, if you instantiate MyObject with the same id, you get the same instance:

a = MyObject(1)
b = MyObject(2)
c = MyObject(2)

a is b  # False
b is c  # True

Upvotes: 16

S.Lott
S.Lott

Reputation: 391952

First, use Upper Case Class Names in Python.

Second, use a Factory design pattern to solve this problem.

class MyObject( object ):
    def __init__( self, args ):
        pass # Something Expensive

class MyObjectFactory( object ):
    def __init__( self ):
        self.pool = {}
    def makeMyObject( self, args ):
        if args not in self.pool:
            self.pool[args] = MyObject( args )
        return self.pool[args]

This is much simpler than fooling around with new and having class level pools of objects.

Upvotes: 9

Related Questions