离线下载
PDF版 ePub版

qyuhen · 更新于 2018-11-28 11:00:43

元类

类型对象地位超然,负责创建对象实例,控制对象行为 (方法)。那么类型对象又由谁来创建呢?—— 元类 (metaclass),也就是类型的类型。

New-Style Class 的默认元类是 type。

>>> class Data(object): pass

>>> Data.__class__
<type 'type'>

>>> type.__class__   # 最终的类型就是 type,包括 type 自己。
<type 'type'>

关键字 class 会被编译成元类创建类型对象指令。

>>> Data = type("Data", (object,), {"x": 1})  # class 的实际行为。

>>> Data.__class__
<type 'type'>

>>> Data.__base__
<type 'object'>

>>> Data.x
1

正因为 class 和 def 一样是指令,我们可以在任何地方创建类型对象。

>>> def test():
...     class Data(object): pass
...     return Data

>>> Data = test()

>>> Data.__name__
'Data'

>>> type(Data)
<type 'type'>

>>> Data()
<__main__.Data object at 0x10659f4d0>

现在可以理清几者的关系,以及创建顺序了。

class = metaclass(...)   # 元类创建类型
instance = class(...)   # 类型创建实例

instance.__class__ is class  # 实例的类型
class.__class__ is metaclass  # 类型的类型

metaclass

除了使用默认元类 type 以外,还可以用 metaclass 属性指定自定义元类,以便对类型对象创建过程进行干预。

>>> class InjectMeta(type):
...     def __new__(cls, name, bases, attrs):
...         t = type.__new__(cls, name, bases, attrs)
...
...     def print_id(self): print hex(id(self))
...     t.print_id = print_id    # 为类型对象添加实例方法。
...     t.s = "Hello, World"    # 添加静态字段。
...
...     return t

>>> class Data(object):
...     __metaclass__ = InjectMeta    # 显式指定元类。

>>> Data.__metaclass__
<class '__main__.InjectMeta'>

>>> Data.__class__
<class '__main__.InjectMeta'>

>>> dir(Data)
['__class__', ... 'print_id', 's']

>>> Data.s
'Hello, World'

>>> Data().print_id()
0x10659d850

自定义元类通常都从 type 继承,习惯以 Meta 结尾,就像抽象元类 abc.ABCMeta 那样。代码很简单,只需注意 newinit 方法参数的区别就行了。

>>> class InjectMeta(type):
...     def __new__(cls, name, bases, attrs):
...         print "class:", cls      # cls = InjectMeta
...         print "name:", name
...         print "bases:", bases
...         print "attrs:", attrs
...         return type.__new__(cls, name, bases, attrs)
...
...     def __init__(cls, name, bases, attrs):
...         print "class:", cls      # cls = Data
...         type.__init__(cls, name, bases, attrs)

>>> class Data(object):
...     __metaclass__ = InjectMeta     # 自定义元类
...     x = 1
...     def test(self): pass

class: <class '__main__.InjectMeta'>
name: Data
bases: (<type 'object'>,)
attrs: {
        'test': <function test at 0x1065370c8>,
        'x': 1,
        '__module__': '__main__',
        '__metaclass__': <class '__main__.InjectMeta'>
}

class: <class '__main__.Data'>

当解释器创建类型对象时,会按以下顺序查找 metaclass 属性。

class.__metaclass__ -> bases.__metaclass__ -> module.__metaclass__ -> type

这也是为什么在模块中可以用 metaclass 为所有类型指定默认元类的缘故。

虽然惯例将元类写成 type 的派生类,但也可以用函数代替。

>>> def inject_meta(name, bases, attrs):
...     t = type(name, bases, attrs)
...     t.s = "Hello, World"
...     return t

>>> class Data(object):
...     __metaclass__ = inject_meta

>>> Data.__metaclass__
<unbound method Data.inject_meta>

>>> Data.s
'Hello, World'

magic

对象行为由类型决定,实例不过存储了状态数据。那么,当我们控制了类型对象的创建,也就意味着可以让对象的实际行为和代码存在极大的差异。这是魔法的力量,也是 Python 核心开发人员 Tim Peters 说出下面这番话的原因 (想必你对他的 import this 很熟悉)。

Metaclasses are deeper magic than 99% of users should ever worry about. If you wonder whether you need them, you don't (the people who actually need them know with certainty that they need them, and don't need an explanation about why). Tim Peters (c.l.p post 2002-12-22)

试着写两个简单的例子练练手。

静态类 (static class): 不允许创建实例,通常作为工具类 (Utility) 存在。

>>> class StaticClassMeta(type):
...     def __new__(cls, name, bases, attr):
...         t = type.__new__(cls, name, bases, attr)
...
...     def ctor(cls, *args, **kwargs):
...         raise RuntimeError("Cannot create a instance of the static class")
...     t.__new__ = staticmethod(ctor)
...
...     return t

>>> class Data(object):
...     __metaclass__ = StaticClassMeta

>>> Data()
RuntimeError: Cannot create a instance of the static class

密封类 (sealed class): 禁止被继承。

>>> class SealedClassMeta(type):
...     _types = set()
...
...     def __init__(cls, name, bases, attrs):
...         if cls._types & set(bases):   # 判断当前类型基类是否是 sealed class。
...             raise SyntaxError("Cannot inherit from a sealed class")
...         cls._types.add(cls)    # 将当前类型加入到禁止继承集合。

>>> class A(object):
...     __metaclass__ = SealedClassMeta

>>> class B(A): pass
SyntaxError: Cannot inherit from a sealed class
上一篇: 描述符 下一篇: 字符串