【问题标题】:Return class instance instead of creating a new one if already existing如果已经存在,则返回类实例而不是创建一个新实例
【发布时间】:2023-03-08 15:39:01
【问题描述】:

我为我正在进行的一些实验室实验的结果定义了一个名为 Experiment 的类。这个想法是创建一种数据库:如果我添加一个实验,它将在退出之前被腌制到一个数据库,并在启动时重新加载(并添加到类注册表中)。

我的班级定义是:

class IterRegistry(type):
    def __iter__(cls):
        return iter(cls._registry)


class Experiment(metaclass=IterRegistry):
    _registry = []
    counter = 0

    def __init__(self, name, pathprotocol, protocol_struct, pathresult, wallA, wallB, wallC):
        hashdat = fn.hashfile(pathresult)
        hashpro = fn.hashfile(pathprotocol)
        chk = fn.checkhash(hashdat)
        if chk:
            raise RuntimeError("The same experiment has already been added")
        self._registry.append(self)
        self.name = name
        [...]

虽然fn.checkhash 是一个检查包含结果的文件的哈希值的函数:

def checkhash(hashdat):
    for exp in cl.Experiment:
        if exp.hashdat == hashdat:
            return exp
    return False

这样,如果我添加以前添加的实验,它就不会被覆盖。

如果已经存在,是否有可能以某种方式返回现有实例而不是引发错误? (我知道在__init__ 块是不可能的)

【问题讨论】:

    标签: python python-3.x class


    【解决方案1】:

    如果你想自定义创建而不是仅仅在新创建的对象中初始化,你可以使用__new__

    class Experiment(metaclass=IterRegistry):
        _registry = []
        counter = 0
    
        def __new__(cls, name, pathprotocol, protocol_struct, pathresult, wallA, wallB, wallC):
            hashdat = fn.hashfile(pathresult)
            hashpro = fn.hashfile(pathprotocol)
            chk = fn.checkhash(hashdat)
            if chk:                      # already added, just return previous instance
                return chk
            self = object.__new__(cls)   # create a new uninitialized instance
            self._registry.append(self)  # register and initialize it
            self.name = name
            [...]
            return self                  # return the new registered instance
    

    【讨论】:

    • 谢谢。这似乎是最好的解决方案,但我有一个简短的问题:我一直都知道覆盖 __new__ 并不是一个好习惯。从文档中:“一般来说,你不需要覆盖__new__,除非你继承了一个不可变类型,比如str、int、unicode或tuple。”初始化__new__中的所有属性可以吗?
    • @david23:当您询问新实例时返回现有实例不是常见的用例,它是覆盖__new__ 的正确实例之一,另一个是不可变类型。
    【解决方案2】:

    尝试这样做(非常简单的示例):

    class A:
        registry = {}
    
        def __init__(self, x):
            self.x = x
    
        @classmethod
        def create_item(cls, x):
            try:
                return cls.registry[x]
            except KeyError:
                new_item = cls(x)
                cls.registry[x] = new_item
                return new_item
    
    
    A.create_item(1)
    A.create_item(2)
    A.create_item(2)  # doesn't add new item, but returns already existing one
    

    【讨论】:

    • 感谢您的回答。这是好习惯吗?有了这个解决方案,其实我会定义__init__之外的所有属性,而且我还需要通过显式调用方法create_item()来实例化对象
    • 我在答案中编辑了代码,现在它在初始化实例时使用__init__() 方法。是的,您需要调用A.create_item() 而不是A(),但恕我直言,它比__new__() 的“神奇”覆盖更邪恶
    • 还将registry 重写为dict 集合,这样从中提取值可能更有效。
    • 如果您需要更多构造函数参数,只需将它们添加到create_item() 方法中,然后只使用必要的参数(如果有多个则加入tuple)作为键。
    【解决方案3】:

    经过四年的问题,我来到这里,Serge Ballesta 的回答帮助了我。我用更简单的语法创建了这个例子。

    如果baseNone,它将始终返回创建的第一个对象。

    class MyClass:
        instances = []
    
        def __new__(cls, base=None):
            if len(MyClass.instances) == 0:
                self = object.__new__(cls)
                MyClass.instances.append(self)
    
            if base is None:
                return MyClass.instances[0]
            else:
                self = object.__new__(cls)
                MyClass.instances.append(self)
                # self.__init__(base)
                return self
    
        def __init__(self, base=None):
            print("Received base = %s " % str(base))
            print("Number of instances = %d" % len(self.instances))
            self.base = base
    
    
    R1 = MyClass("apple")
    R2 = MyClass()
    R3 = MyClass("banana")
    R4 = MyClass()
    R5 = MyClass("apple")
    
    print(id(R1), R1.base)
    print(id(R2), R2.base)
    print(id(R3), R3.base)
    print(id(R4), R4.base)
    print(id(R5), R5.base)
    print("R2 == R4 ? %s" % (R2 == R4))
    print("R1 == R5 ? %s" % (R1 == R5))
    

    它给了我们结果

    Received base = apple 
    Number of instances = 2
    Received base = None 
    Number of instances = 2
    Received base = banana 
    Number of instances = 3
    Received base = None 
    Number of instances = 3
    Received base = apple 
    Number of instances = 4
    2167043940208 apple
    2167043940256 None
    2167043939968 banana
    2167043940256 None
    2167043939872 apple
    R2 == R4 ? True
    R1 == R5 ? False
    

    很高兴知道__init__ 总是在__new__return 之前被调用,即使你不调用它(在注释部分)或者你返回一个已经存在的对象。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-21
      • 1970-01-01
      • 2015-01-24
      • 2014-10-10
      • 2013-03-23
      • 2017-10-06
      相关资源
      最近更新 更多