Return class instance instead of creating a new on

2019-07-23 19:32发布

问题:

I defined a class named Experiment for the results of some lab experiments I am conducting. The idea was to create a sort of database: if I add an experiment, this will be pickled to a db before at exit and reloaded (and added to the class registry) at startup.

My class definition is:

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
        [...]

While fn.checkhash is a function that checks the hashes of the files containing the results:

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

So that if I add a previously added experiment, this won't be overwritten.

Is it possible to somehow return the existing instance if already existant instead of raising an error? (I know in __init__ block it is not possible)

回答1:

You can use __new__ if you want to customize the creation instead of just initializing in newly created object:

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


回答2:

Try to do it this way (very simplified example):

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