简体   繁体   中英

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.

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

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.

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])

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