【问题标题】:How to create a class that doesn't re-create an object with identical input parameters如何创建一个不重新创建具有相同输入参数的对象的类
【发布时间】:2021-07-09 16:57:52
【问题描述】:

我正在尝试创建一个不会重新创建具有相同输入参数的对象的类。当我尝试使用用于创建已存在对象的相同参数来实例化一个类时,我只希望我的新类返回一个指向已创建(昂贵创建)对象的指针。这是我迄今为止尝试过的:

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'

这是我第一次尝试覆盖__new__,我确信我不会以正确的方式去做。请让我直截了当。

【问题讨论】:

    标签: python multiton


    【解决方案1】:

    这是一个类装饰器,可以让一个类成为多对象:

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

    (这是 PEP 318 中单例装饰器的轻微变体。)

    然后,为了让你的类成为一个 multiton,使用装饰器:

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

    现在,如果你用相同的 id 实例化 MyObject,你会得到相同的实例:

    a = MyObject(1)
    b = MyObject(2)
    c = MyObject(2)
    
    a is b  # False
    b is c  # True
    

    【讨论】:

    • 这是迄今为止 S.Lott 建议的更清洁、高效和简单的解决方案。
    • @ErikAllik,您写道,“使用__new__ 确实是/a pythonic 的方式。”与使用 __new__ 的解决方案相比,您更喜欢 @Jerry 的解决方案吗?
    • @kuzzooroo:我会说是的——Jerry 的解决方案不那么神奇,更直接,同时也是 Pythonic。
    • 对未来读者信息的小挑剔:为getinstance 的参数使用keyname 之类的名称,而不是id,以避免隐藏Python 内置函数。跨度>
    【解决方案2】:

    首先,在 Python 中使用大写的类名。

    其次,使用Factory设计模式来解决这个问题。

    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]
    

    这比玩弄新的和拥有类级别的对象池要简单得多。

    【讨论】:

    • 这正是最好的方法。完美推荐。
    • 使用__new__ 确实是/a pythonic 的方式,而不是引入另一个类——你的解决方案是惯用的Java。另外,不要在括号内插入空格,也不要在 Python 中使用 camelCase 方法名。
    【解决方案3】:

    这是我对 Jerry 方式的实现,使用数组作为池

    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)
    

    然后以另一种方式使用元类,以便您可以继承功能。这个使用weakreaf valuedicts作为池,所以对象得到更好的垃圾收集

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2017-10-11
      • 2011-08-19
      • 1970-01-01
      • 2014-07-23
      • 1970-01-01
      • 1970-01-01
      • 2011-04-14
      相关资源
      最近更新 更多