在Python中何时内联元类定义?
今天我看到一个关于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
还有其他需要考虑的地方吗?
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__'>
换句话说,如果你想知道“类 A 是什么类型”,你会得到一个清晰且有用的答案——它是主模块中的 Mcl
。然而,如果你想知道“类 B 是什么类型”,答案就没那么有用了:它 说 它是主模块中的 __metaclass__
,但这甚至不是真的:
>>> 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
编写的方便)。通过将 lambda
和 def
做类比,你可以合理地说,对于那些简单到不需要调试或检查的元类,匿名的“嵌套”定义是可以接受的。
在 Python 3 中,“嵌套定义”根本不适用——在这里,元类必须作为关键字参数传递给类,比如 class A(metaclass=Mcl):
,所以在类体内定义 __metaclass__
是没有效果的。我认为这也表明,在 Python 2 代码中嵌套元类定义可能只有在你确定这段代码永远不需要迁移到 Python 3 时才合适(因为这样会让迁移变得更加困难,并且需要为了迁移而去除嵌套的元类定义)——换句话说,就是“一次性”代码,几年后当某个版本的 Python 3 在速度、功能或第三方支持上比 Python 2.7(最后一个 Python 2 版本)有巨大的优势时,这段代码可能就不再存在了。
历史告诉我们,你 期望 是一次性的代码,往往会让你大吃一惊,20 年后仍然存在(而你在同一时间写的“为永恒而作”的代码可能早已被遗忘;-)。这无疑表明应该避免嵌套定义元类。