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

Posted

技术标签:

【中文标题】何时在 Python 中内联元类的定义?【英文标题】:When to inline definitions of metaclass in Python? 【发布时间】:2011-03-29 20:42:50 【问题描述】:

今天我在 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】:

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

基本上,不是给元类一个适当的***名称,您最终会得到一个模块中定义的所有自定义元类,它们基于它们的__module____name__ 属性彼此无法区分(如果需要,这就是 Python 用来形成repr 的方法)。考虑:

>>> 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 类”,答案并不是那么有用:它它是 main 模块中的 __metaclass__,但这甚至不是真的:

>>> 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),如果需要调试或自省,可能会导致类似的混淆。

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

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

期望被抛弃的代码,正如计算的历史向我们展示的那样,有一个可爱的习惯,让你完全惊讶,并且在大约 20 年后仍然存在(也许你写的代码同时“千古”被完全遗忘;-)。这当然似乎建议避免元类的嵌套定义。

【讨论】:

令人印象深刻的答案。 “你要依赖???,和序列化”有一点遗漏。 我不确定pickle 的问题有多大。酸洗似乎工作正常here。 事实上,我认为这种误导性表示是 Python 嵌套类表示中的一个错误。见here。当然,除非嵌套类被 Python 禁止。 @Muhammad,一个类的repr是模块名,点,类名:就是这样。你似乎想要一个不同的规则,但我认为你不理解这些问题(一个类没有引用函数或类,如果有,其中它的class 声明,如果有的话, was,这样可以避免创建一个无用的循环引用)。你对腌制和我做的错误编辑是正确的(这就是为什么我错误地离开了腌制的提及),现在编辑来修复。 @Alex:碰巧,我知道你提到的问题。我看到的错误是所有嵌套类的表示(module.classname)都是错误的。要么不使用该表示,要么禁止嵌套类。或者争辩说这是一个已知问题但不会修复。

以上是关于何时在 Python 中内联元类的定义?的主要内容,如果未能解决你的问题,请参考以下文章

python 元类

Python—元类

使用 Boost.Python 设置包装类的元类

Python从门到精通:元类-01-元类

面向对象之元类

python元类深入解析