I built this auto registering registry to look into a package and import all modules and register any classes that are a subclass of a base class. I use it as a registry for a model's choices in Django and to be able to do a getattr on the class to get properties in a service that processes some data based on choices selected and dynamically finds the right class. Reasoning behind this methodology is because we would have an unknown number of stat classes that will be created in the future. I wanted to avoid forgetting to register them and to hide the implementation details of the choices on the model (The model is absolutely massive). So I wanted a folder that just takes care of that for us. I am trying to determine if this is breaking any best practices, is a good design, safe, efficient and not doing anything downright stupid. (Maybe the dynamic import).Base class for using it other places if need beclass AutoRegClassRegistry(Singleton):    PACKAGE = None    BASE_CLS = None    def __init__(self, force_import=True, *args, **kwargs):        super(AutoRegClassRegistry, self).__init__(*args, **kwargs)        self.force_import = force_import        if self.PACKAGE:            package_dir = os.path.dirname(self.PACKAGE.__file__)            for module in [name for _, name, _ in pkgutil.iter_modules([package_dir])]:                module = {}.{}.format(self.PACKAGE.__name__, module)                if self.force_import:                    _ = __import__(module)                for name, obj in inspect.getmembers(sys.modules[module], inspect.isclass):                    if name == self.BASE_CLS.__name__:                        continue                    if issubclass(obj, self.BASE_CLS):                        self.register(obj)    def register(self):        raise NotImplementedError(You must inherit from this class and override the register func)Project structure- init.py (this ClientStats class in here)- base.py- autoregister    - init.py    - hourly.py    - daily.py    - weekly.py    - monthly.pyImplementationclass ClientStats(AutoRegClassRegistry):        A registry for client stats that also behaves as a tuple when used as a choices field. You can also do a getattr on    this class to get the class, description or short of a stat.        PACKAGE = auto_register    BASE_CLS = BaseStat    def __init__(self, *args, **kwargs):        self.__choices = []        super(ClientStats, self).__init__(*args, **kwargs)    def __getitem__(self, key):        return self.__choices[key]    def __iter__(self):        return iter(self.__choices)    def register(self, cls):        cls_short = getattr(cls, SHORT, None)        if not cls_short:            raise AttributeError(CLS {} doesn't have attribute 'SHORT' which is required to register to ClientStats)        cls_desc = getattr(cls, DESCRIPTION, None)        if not cls_desc:            raise AttributeError(                CLS {} doesn't have attribute 'DESCRIPTION' which is required to register to ClientStats)        if hasattr(self, cls_short):            raise AttributeError(ClientStats already has a {} registered.format(cls_short))        setattr(self, cls_short, cls_short)        setattr(self, {}_DESC.format(cls_short), cls_desc)        setattr(self, {}_CLS.format(cls_short), cls)        self.__choices.append((cls_short, getattr(self, {}_DESC.format(cls_short))))CLIENT_STATS = ClientStats()Example class that gets registeredclass DailyStatExample(BaseStat):    SHORT = DSE    DESCRIPTION = Example stat classSample usage of the ClientStats class:models.pyclass ExampleModel(models.Model):    client_stats = MultiSelectSlugField(choices=CLIENT_STATS)script.pystats = ExampleModel.objects.get(pk=1).client_statsfor stat in stats:    klass = getattr(CLIENT_STATS, {}_CLS.format(stat)    klass_inst = klass()    klass.calculate(arg1, arg2)
Auto registering class registry
python;django;dynamic programming
Whyfor module in [name for _, name, _ in pkgutil.iter_modules([package_dir])]:and notfor _, module, _ in pkgutil.iter_modules([package_dir]):? If it's about iterating eagerly, using list would work better than a list comprehension._ = __import__(module)is also weird; why not just __import__(module)?It makes sense for AutoRegClassRegistry to be an ABC, but by no means is this a requirement. It's worth noting thatdef register(self):    raise NotImplementedError(You must inherit from this class and override the register func)has the wrong number of arguments, so will give an unhelpful error instead.Don't use name mangling for self.__choices = [] unless there's good reason; _choices works just as well.cls_short = getattr(cls, SHORT, None)if not cls_short:    raise AttributeError(CLS {} doesn't have attribute 'SHORT' which is required to register to ClientStats)seems like it breaks EAFTP;cls_short = cls.SHORTwould work just as well andtry:    cls_short = cls.SHORTexcept AttributeError:    raise AttributeError(CLS {} doesn't have attribute 'SHORT' which is required to register to ClientStats)would be more descriptive of intent. The same happens immediately after.The wholeif hasattr(self, cls_short):    raise AttributeError(ClientStats already has a {} registered.format(cls_short))setattr(self, cls_short, cls_short)setattr(self, {}_DESC.format(cls_short), cls_desc)setattr(self, {}_CLS.format(cls_short), cls)looks really backwards, though; why not just put this in a simple data structure? Why does it have to be through messy attribute accesses.This whole thing feels really dynamic, and dynamism should be a last resort. But I've never touched Django, so I can't really say whether what you're doing is justified or not.As I said, I've never touched Django so I can't really say whether this could work, but I'd be tempted to do something more likeDailyStatExample = ClientStats.new(DSE, Example stat class)or@ClientStats.register(DSE, Example stat class)class DailyStatExample:    ...