何时在 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
编码的函数那么方便)。通过与lambda
与def
进行类比,您可以合理地声称匿名、“嵌套”定义对于非常简单的元类是可以的,如此简单明了,以至于不需要调试或自省。
在 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 中内联元类的定义?的主要内容,如果未能解决你的问题,请参考以下文章