15

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

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

什么时候使用这样的内联定义才有意义?

进一步的论点:

一种说法是创建的元类不能在其他地方使用这种技术重用。一个反驳的论点是,使用元类的一个常见模式是定义一个元类并在一个类中使用它,然后继承该类。例如,在保守的元类中,定义

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

还有其他考虑吗?

4

1 回答 1

20

像嵌套类定义的所有其他形式一样,嵌套元类对于多种“生产用途”可能更加“紧凑和方便”(只要您可以不重用该元类,除非通过继承),但可能有点不方便调试和自省。

基本上,不是给元类一个适当的顶级名称,而是最终在一个模块中定义的所有自定义元类根据它们的__module____name__属性彼此不可区分(这是 Python 用来形成它们的reprif需要)。考虑:

>>> class Mcl(type): pass
... 
>>> class A: __metaclass__ = Mcl
...
>>> class B:
...   class __metaclass__(type): pass
... 
>>> type(A)
<class '__main__.Mcl'>
>>> type(B)
<class '__main__.__metaclass__'>

IOW,如果你想检查“哪个类型是 A 类”(元类是类的类型,请记住),你会得到一个清晰而有用的答案——它Mcl在主模块中。但是,如果您想检查“哪种类型是 B 类”,答案并不是那么有用:它说它在模块__metaclass__main,但事实并非如此:

>>> import __main__
>>> __main__.__metaclass__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'module' object has no attribute '__metaclass__'
>>> 

......实际上没有这样的事情;repr 具有误导性并且不是很有帮助;-)。

类的 repr 本质上是'%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)创建的类,如果需要调试或自省,可能会导致类似的混淆。

因此,如果您永远不需要调试或以其他方式反省该代码,嵌套元类是可以的,并且如果这样做的人理解这个怪癖(尽管它永远不会像使用一个好听的真实姓名那样方便,当然——就像调试一个lambdadef通过与lambdavs类比,def您可以合理地声称匿名的“嵌套”定义对于非常简单的元类是可以的,如此简单明了,以至于不需要调试或内省。

在 Python 3 中,“嵌套定义”不起作用——在那里,必须将元类作为关键字参数传递给类,如 in class A(metaclass=Mcl):,因此__metaclass__在正文中定义无效。我相信这也表明 Python 2 代码中的嵌套元类定义可能只有当您确定代码永远不需要移植到 Python 3 时才合适(因为您使该移植变得更加困难,并且需要为此目的嵌套元类定义)——“一次性”代码,换句话说,当 Python 3 的某些版本在速度、功能或第三方面获得巨大的、引人注目的优势时,它不会在几年内出现。派对支持,超过 Python 2.7(Python 2 的最后一个版本)。

正如计算机的历史向我们展示的那样,您期望被丢弃的代码有一个令人惊讶的可爱习惯,并且在大约 20 年后仍然存在(而您在同一时间编写的“多年来”的代码可能完全是忘记了;-)。这当然似乎建议避免元类的嵌套定义。

于 2010-08-14T14:52:12.893 回答