【问题标题】:Generic metaclass to keep track of subclasses?用于跟踪子类的通用元类?
【发布时间】:2011-04-24 08:08:34
【问题描述】:

我正在尝试编写一个通用元类来跟踪子类

因为我希望它是通用的,我不想在这个元类中硬编码任何类名,因此我想出了一个生成正确元类的函数,例如:

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

这样我可以调用它来为特定的 root 类生成一个元类:

__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__ 在读取类定义时会进行特殊处理,如Customizing class creation 中定义的那样。

我正在寻找建议以执行此操作(在运行时更改类的元类以将其应用于其子类,或任何其他替代方法)。

【问题讨论】:

  • 请不要这样做。追随你的人会把它撕掉,因为它太复杂了。请使用创建适当子类对象的工厂函数。

标签: python metaclass


【解决方案1】:

Python 会自动为新式类执行此操作,如 answer 中提到的与此处 How to find all the subclasses of a class given its name? 类似的问题。

【讨论】:

  • 虽然这在技术上确实可以跟踪,但根据您的使用情况,它可能不是很有效。它返回一个列表,因此如果您要查找具有特定属性的类,则必须遍历整个列表。使用元类可以让您根据需要有效地索引子类。
  • @Cerin:也许......如果高效率是一个问题 - 无法从没有指定如何使用信息的问题中确定。无论如何,我有兴趣看到您的想法的具体实现,因为所有其他答案目前也是基于列表的——所以请随意添加您自己的一个。
  • @martinaeu,我的实现与 aaronasterling 的实现相同,只是注册表是字典而不是列表,而键是您想为每个类使用的任何散列表示。我目前正在使用它作为 Django 中 register() 模式的替代方案,将 ModelForm 类与唯一的 slug 相关联,以便快速进行 URL 调度。
  • @Cerin:aaronasterling 的回答有一些问题,对 Django 的引用对我们许多人来说意义不大,所以再次发布一些代码或编辑现有答案。
  • 你过于防御了。我没有批评的意思。我只提到了我认为其他人可能会考虑的一点。
【解决方案2】:

我想你想要这样的东西(未经测试):

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 子句中,这样该类就不会将自己注册为子类。

【讨论】:

  • 其实你的SubclassTracker没有包含任何对root类型的硬编码引用,所以不需要函数来创建它,所以我直接定义了一个SubclassTracker类并完全忘记了 make_subtracker 。工作正常,谢谢!
  • @Carles,如果没有定义_registry,您上次发表评论时的版本将与多个类中断。我修好了,所以看看新的。它更干净。实际上,我什至没有想到要取消工厂功能。
  • FWIW,正如目前所写的那样,Root 将把自己添加到它的_registry 列表中,即使从技术上讲它不是它自己的子类。这可能是可取的,也可能是不可取的。
  • 此外,如果你从Root 派生一个类,它不仅会在其_registry 列表中包含自己,而且还会在其中包含Root,即使它们都不是子类——似乎很可能是不受欢迎的。
【解决方案3】:

这是我一直在玩的东西(有效):

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

【讨论】:

    猜你喜欢
    • 2012-03-26
    • 2020-05-26
    • 2019-11-17
    • 2015-01-17
    • 2015-01-06
    • 1970-01-01
    • 2014-09-20
    • 2012-01-10
    • 1970-01-01
    相关资源
    最近更新 更多