python: defining registry in base class

2019-08-26 03:25发布

问题:

I'm implementing enumeration using a base class that defines a variety of methods. The actual enumerations are subclasses of that, with no additional methods or attributes. (Each subclass is populated with its own values using the constructor defined in the base class).

I use a registry (a class attribute that stores all the instances of that class). Ideally, I'd like to avoid defining it in each subclass. Unfortunately, if I define it in the base class, all the subclasses will end up sharing the same registry.

What's a good approach here?

Below is the implementation in case it helps (it's based on @jchl comment in python enumeration class for ORM purposes).

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

class EnumType(metaclass = IterRegistry):
    _registry = {}
    _frozen = False
    def __init__(self, token):
        if hasattr(self, 'token'):
            return
        self.token = token
        self.id = len(type(self)._registry)
        type(self)._registry[token] = self

    def __new__(cls, token):
        if token in cls._registry:
            return cls._registry[token]
        else:
            if cls._frozen:
                raise TypeError('No more instances allowed')
            else:
                return object.__new__(cls)

    @classmethod
    def freeze(cls):
        cls._frozen = True

    def __repr__(self):
        return self.token

    @classmethod
    def instance(cls, token):
        return cls._registry[token]

class Enum1(EnumType): pass
Enum1('a')
Enum1('b')
for i in Enum1:
  print(i)

# not going to work properly because _registry is shared
class Enum2(EnumType): pass

回答1:

As you already have a metaclass you might as well use it to put a add a separate _registry attribute to each subclass automatically.

class IterRegistry(type):
    def __new__(cls, name, bases, attr):
        attr['_registry'] = {} # now every class has it's own _registry
        return type.__new__(cls, name, bases, attr)


回答2:

Marty Alchin has a very nice pattern for this: see his blog entry.



回答3:

What if you share the same registry, but with sub-registries per class, i.e.

if cls.__name__ not in self._registry:
    self._registry[cls.__name__] = {}
self._registry[cls.__name__][token] = cls

You actually don't even need cls.__name__, you should be able to use cls itself as key.