Generic metaclass to keep track of subclasses?

匿名 (未验证) 提交于 2019-12-03 03:06:01

问题:

I'm trying to writing a generic metaclass for tracking subclasses

Since I want this to be generic, I didn't want to hardcode any class name within this metaclass, therefore I came up with a function that generates the proper metaclass, something like:

def make_subtracker(root):     class SubclassTracker(type):         def __init__(cls, name, bases, dct):             print('registering %s' % (name,))             root._registry.append(cls)             super(SubclassTracker, cls).__init__(name, bases, dct)     return SubclassTracker 

This way I could invoke it to generate a metaclass for a specific root class with:

__metaclass__ = make_subtracker(Root) 

Here is where I bump into a problem. I cannot do this:

class Root(object):    _registry = []    __metaclass__ = make_subtracker(Root) 

...because Root is not defined yet when I use make_subtracker(Root). I tried adding the metaclass attribute later, so that at least it can be applied in subclasses:

class Root(object):    _registry = []  Root.__metaclass__ = make_subtracker(Root) 

...but this doesn't work. metaclass has a special processing when the class definition is read, as defined in http://docs.python.org/reference/datamodel.html#customizing-class-creation

I'm looking for suggestions in order to do this (either change a class' metaclass at runtime in a way that it is applied to its subclasses, or any other alternative).

回答1:

Python does this automatically for new-style classes, as mentioned in this answer to the similar queston How can I find all subclasses of a given class in Python? here.



回答2:

I think you want something like this (untested):

class SubclassTracker(type):     def __init__(cls, name, bases, dct):         if not hasattr(cls, '_registry'):             cls._registry = []         print('registering %s' % (name,))         cls._registry.append(cls)         super(SubclassTracker, cls).__init__(name, bases, dct) 

Then, for Python 2, you can invoke it like:

class Root(object):     __metaclass__ = SubclassTracker 

for Python 3

class Root(object, metaclass=SubclassTracker): 

Note that you don't need to stick the _registry attribute on there because stuff like that is what metaclasses are for. Since you already happen to have one laying around... ;)

Note also that you might want to move the registration code into an else clause so that the class doesn't register itself as a subclass.



回答3:

Here's something I've been playing around with (that works):

def sublass_registry():     ''' Create a metaclass to register subclasses '''      class SublassRegistryMeta(type):         def __init__(cls, name, bases, classdict):             if classdict.get('__metaclass__') is SublassRegistryMeta:                 SublassRegistryMeta.lineage = [cls] # put root class at head of a list             else:                 # sublclasses won't have __metaclass__ explicitly set to this class                 # we know they're subclassees because this ctor is being called for them                 SublassRegistryMeta.lineage.append(cls) # add subclass to list             type.__init__(cls, name, bases, classdict)      return SublassRegistryMeta  def subclasses(cls):     ''' Return a list containing base and subclasses '''      try:         if cls.__metaclass__.lineage[0] is cls: # only valid for a root class             return cls.__metaclass__.lineage     except AttributeError:         pass     return None  class Car(object): # root class     __metaclass__ = sublass_registry()  class Audi(Car): # inherits __metaclass__     pass  class Ford(Car): # inherits __metaclass__     pass  class Audi2(Audi): # sub-subclass also inherits __metaclass__     pass  print subclasses(Car) # [<class '__main__.Car'>, <class '__main__.Audi'>, <class '__main__.Ford'>, <class '__main__.Audi2'>] print subclasses(Audi) # None 


标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!