通用元类用于追踪子类?

11 投票
3 回答
8364 浏览
提问于 2025-04-16 05:22

我正在尝试写一个通用的元类,用来追踪子类。

因为我想让这个元类通用,所以不想在里面写死任何类名。因此,我想出了一个函数,可以生成合适的元类,类似于:

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

这样我就可以调用这个函数,为一个特定的类生成元类,方法是:

__metaclass__ = make_subtracker(Root)

在这里,我遇到了一个问题。我不能这样做:

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

...因为在我使用make_subtracker(Root)的时候,Root还没有定义。我试着后来添加__metaclass__属性,这样至少可以在子类中应用:

class Root(object):
   _registry = []

Root.__metaclass__ = make_subtracker(Root)

...但这也不行。__metaclass__在读取类定义时有特殊的处理,具体可以参考自定义类创建

我在寻找建议,想要实现这个功能(要么在运行时改变一个类的元类,使其应用于子类,要么其他替代方案)。

3 个回答

2

这是我最近在尝试的一些东西(它是有效的):

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
11

我觉得你可能想要这样的东西(没测试过):

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)

然后,对于Python 2,你可以这样调用:

class Root(object):
    __metaclass__ = SubclassTracker

对于Python 3:

class Root(object, metaclass=SubclassTracker):

注意,你不需要在这里加上_registry这个属性,因为这种事情是元类的作用。既然你正好有一个在手边... ;)

另外,你可能想把注册代码放到一个else语句里,这样这个类就不会把自己注册为子类了。

11

在Python中,对于新式类,它会自动处理这个问题。你可以在这个回答中找到相关信息,回答的是一个类似的问题如何找到一个类的所有子类?

撰写回答