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).
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.
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.
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
来源:https://stackoverflow.com/questions/3915315/generic-metaclass-to-keep-track-of-subclasses