何时在Python中内联定义元类?

时间:2010-08-14 14:21:36

标签: python metaclass

今天我在Python here中遇到了一个令人惊讶的元类定义,其中元类定义有效内联。相关部分是

class Plugin(object):
    class __metaclass__(type):
        def __init__(cls, name, bases, dict):
            type.__init__(name, bases, dict)
            registry.append((name, cls))

何时使用这样的内联定义是有意义的?

进一步的论点:

一种方式论证是创建的元类在其他地方使用这种技术是不可重用的。一个反驳论点是,使用元类的一个常见模式是定义元类并在一个类中使用它,然后从中继承。例如,在a conservative metaclass定义中

class DeclarativeMeta(type):
    def __new__(meta, class_name, bases, new_attrs):
        cls = type.__new__(meta, class_name, bases, new_attrs)
        cls.__classinit__.im_func(cls, new_attrs)
        return cls
class Declarative(object):
    __metaclass__ = DeclarativeMeta
    def __classinit__(cls, new_attrs): pass

本来可以写成

class Declarative(object):  #code not tested!
    class __metaclass__(type):
        def __new__(meta, class_name, bases, new_attrs):
            cls = type.__new__(meta, class_name, bases, new_attrs)
            cls.__classinit__.im_func(cls, new_attrs)
            return cls
    def __classinit__(cls, new_attrs): pass

还有其他考虑吗?

1 个答案:

答案 0 :(得分:19)

与所有其他形式的嵌套类定义一样,嵌套的元类可能更“紧凑和方便”(只要你没有重用那个元类除了通过继承)对于许多种类的“生产用途”,但是对于调试和内省来说可能有些不方便。

基本上,不是给元类一个正确的顶级名称,而是基于它们__module__和{{1属性(如果需要,这是Python用来形成__name__的东西)。考虑:

repr

IOW,如果你想检查“哪个类是A类”(一个元类是类的类型,请记住),你会得到一个明确而有用的答案 - 它是主模块中的>>> class Mcl(type): pass ... >>> class A: __metaclass__ = Mcl ... >>> class B: ... class __metaclass__(type): pass ... >>> type(A) <class '__main__.Mcl'> >>> type(B) <class '__main__.__metaclass__'> 。但是,如果你想检查“哪个类型是B类”,答案就不那么有用了:Mcl模块中说它是__metaclass__,但那不是甚至是真的:

main
实际上, 没有这样的事情;那个repr是误导性的,不是很有帮助; - )。

一个类的repr本质上是>>> import __main__ >>> __main__.__metaclass__ Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'module' object has no attribute '__metaclass__' >>> - 一个简单,有用且一致的规则 - 但在许多情况下,例如,'%s.%s' % (c.__module__, c.__name__)语句在模块范围内不是唯一的,或者不在模块范围(但在一个函数或类体内),或者甚至不存在(类当然可以在没有class语句的情况下通过显式调用它们的元类来构建),这可能有些误导(并且最好的解决方案是尽可能避免那些特殊情况,除非使用它们可以获得实质性的优势)。例如,考虑:

class

在同一范围内有两个>>> class A(object): ... def foo(self): print('first') ... >>> x = A() >>> class A(object): ... def foo(self): print('second') ... >>> y = A() >>> x.foo() first >>> y.foo() second >>> x.__class__ <class '__main__.A'> >>> y.__class__ <class '__main__.A'> >>> x.__class__ is y.__class__ False 语句,第二个重新绑定名称(此处为class),但现有实例引用的是名称的第一个绑定,而不是名称 - - 所以两个类对象都保留,只能通过其实例的A(或type属性)访问(如果有的话 - 如果没有,则第一个对象消失) - 两个类具有相同的名称和模块(因此表示相同),但它们是不同的对象。嵌套在类或函数体中的类,或通过直接调用元类(包括__class__)创建的类,如果需要调试或内省,可能会引起类似的混淆。

因此,如果您永远不需要调试或以其他方式内省该代码,那么嵌套元类是可以的,并且如果这样做的人理解这个怪癖可以一直生活(尽管它永远不会像使用一个好的那样方便,实名,当然 - 就像调试用type编码的函数一样,不可能像调试用lambda编码的函数那么方便。通过类比def vs lambda,您可以合理地声称匿名的“嵌套”定义对于非常简单的元类来说是可以的,例如没有脑子,没有必要进行调试或内省。

在Python 3中,“嵌套定义”不起作用 - 在那里,元类必须作为关键字参数传递给类,如def中所示,因此定义class A(metaclass=Mcl): in身体没有效果。我相信这也表明Py​​thon 2代码中的嵌套元类定义可能只有在您确定代码永远不需要移植到Python 3时才适用(因为您使端口变得更加困难,并且需要为了这个目的而去嵌入元类定义 - 换句话说,当一些版本的Python 3获得速度,功能或第三方的巨大,引人注目的优势时,它将在几年后出现。派对支持,通过Python 2.7(Python 2的最后一个版本)。

正如计算机的历史向我们展示的那样,期望的代码是一次性的,有一种让人惊讶的习惯,并且在20年后仍然存在(可能是你写的代码)同时“岁月”完全被遗忘;-)。这肯定会建议避免使用嵌套的元类定义。