"MetaClassInPython"《Python In a Nutshell(2003)》5.4 python中的元类(metaclass)

5.4 Metaclasses(元类)

任何对象,即使是类对象,都有一个类型.在python中,类型和类都是第一层次对象.一个类对象的类型称为元类.[见小注]一个对象的行为很大程度上由该对象的类型决定.对于类来说当然也不例外:一个类的行为很大程度取决于该类的元类. 元类是一个高级主题,当你第一次阅读本节时,你可以跳过本节.然而,深入理解元类能助你更深入的理解python.有时你也会有自定义元类的需要.

小注

严格来讲, 类C的类型(C的metaclass)主要是指 C的实例的元类,而不仅仅是C类本身. 不过这里面仅有极细微的差别,如果想探个究竟,不妨自己实践一下. 传统类与新型类的具有不同的类型(元类),这是传统类与新型类型的最主要区别. 就是这个原因造成了两者的不同行为.

   1 class Classic: pass
   2 class Newstyle(object): pass
   3 print type(Classic)                  # prints: <type 'class'>
   4 print type(Newstyle)                 # prints: <type 'type'>

传统类的类型是 标准库中 types 模块中定义的 types.ClassType. 新型类的类型是内建对象 type. type 也是所有python内建类型的元类,包括type自己.(type是type自己的元类).( print type(type) 也会打印出 <type 'type'>)

5.4.1 Python 如何判断一个类的元类

To execute a class statement, Python first collects the base classes into a tuple t (an empty one, if there are no base classes) and executes the class body in a temporary dictionary d. Then, Python determines the metaclass M to use for the new class object C created by the class statement. 执行 类定义 语句前, Python首先将基类信息收集到一个 tuple t中(若没有基类,则生成一个空的 t), 然后在一个临时的字典 d 中运行 类定义语句, 到类几乎创建完成时, Python 才能知道新类对象 C 的元类 M 是什么.

若临时字典 d 拥有 key '__metaclass__' , M 就是 d['__metaclass__'], 这样你可以在类 C 的定义语句中显式定义一个属性 metaclass. 否则, 如果 t 不是空的(也就是类C有一个或多个基类), M 的值就是 type(t[0]), 也就是 C 第一个基类的元类. 显然所有从 object 继承来的类都是 新型类 . 由于 type(object)返回 type , 不论类C继承自 object正常是其它内建类型,它都拥有相同的元类 type . 结论:新型类=元类是 type 的类.

当类C 没有基类, 但是当前模块拥有一个名为 metaclass 全局变量时, M就取该全局变量的值. 这允许你在一个模块中定义类时不必显式的指定 object 为基类,同样可以定义 新型类. 只需在模块的开始处加入这样一条语句: (weizhong的建议:这怎么看都不象一个好主意.建议还是显式的的定义一个新型类) __metaclass_ = type 如果Python实在找不到元类信息, M 就取默认值 types.ClassType.

5.4.2 元类如何创建一个类

确定了 M 以后, Python就提供三个参数给M: 字符串类名,基类列表tuple t 及临时字典 d ,调用 M 的结果就是返回 类对象 C,至此类定义语句才真正执行完毕. 这实际上是 type M 的实例化过程, 也就是说 隐式调用了 M.__init__(C,类名,t,d),而 C 就是 M.__new__(M,类名,t,d)的返回值. 这与实例化一个新型类行为极其相似(可以这样理解,类是元类的实例,实例是类的实例).

当类对象 C 创建完毕, 类C和它的类型(元类)之间的关系,就象任何其它对象与它们的类型之间的关系是一样的. 举例来说:当你调用 类C时(生成一个C的实例), M.call方法就会以类C作为它的第一个参数被调用.

注意在本章前面5.2.4.4节对新型类的描述. 调用C必然调用元类 M.call方法(无论类C中是否有定义call方法), 这与传统对象模型是不兼容的. 传统对象模型中,类实例若重新绑定了call方法,就会覆盖掉类中定义的call方法,即使是隐式调用. 新的对象模型避免了不得不在类与其元类之间维护一种特殊关系. 尽量避免特殊行为使Python伟大力量的关键: Python拥有很少的,简单的但却是一致的规则.

5.4.2.1定义和使用你自己的元类

不用自己定义元类,通过从一个类型中继承并重载某些方法,你可以使用new,init,getattribute等方法完成几乎所有的工作. 虽然很少需要自己定义元类,但在真有需要时自定义元类能运行的更快更好,这是因为在类创建那一刻所有特殊处理就已完成.自定义元类允许你定义整个类的框架,只要你想让类实现什么功能,就自己写代码去实现什么功能.除此之外,类对象的某些行为只能在元类中定制. 下面这个简单的例子演示了通过自定义元类改变类对象的字符串表示:

   1 class MyMeta(type):
   2     def __str__(cls): return "Beautiful class '%s'"%cls.__name__
   3 class MyClass:
   4     __metaclass__ = MyMeta
   5 x = MyClass(  )
   6 print type(x)

严格来说,由你自定义元类衍生出来的类既不是传统类,也是不新型类.元类定义了这些类及它们实例的一切行为. 然而在实践中,你的自定义元类几乎总是继承自一个内建类型.因此,由你自定义元类衍生而来的类可以视为新型类的一个变种. (我的感觉: 类和元类都是一些特殊的函数,不同之处在于执行类函数生成一个实例对象,执行一个元类函数,生成一个类对象)

5.4.2.2 元类示例

如果我们不考虑类 C 的类型, 则一个C的实例不过就是一捆由固定名字(属性名)引用的一组数据. 在python中,我们能很容易的定义一个属性名字不固定的类(Bunch class).

   1 class Bunch(object):
   2     def __init__(self, **fields): self.__dict__ = fields
   3 p = Bunch(x=2.3, y=4.5)
   4 print p                     # prints: <__main__.Bunch object at 0x00AE8B10>

通过定义一个自定义元类,我们可以开发一种新的类,这种类在类创建时就定死了属性名. 下例(例5-1)定义了一个元类:metaMetaBunch, 还有一个类, MetaBunch. 代码如下:

Example 5-1. The metaMetaBunch metaclass

   1 import warnings
   2 class metaMetaBunch(type):
   3     """
   4     metaclass for new and improved "Bunch": implicitly defines __slots__,
   5    __init__and __repr__from variables bound in class scope.
   6    为Bunch类定义一个新的增强的元类:在类范围内隐式的定义了__slots__,__init___及__repr__方法
   7     A class statement for an instance of metaMetaBunch (i.e., for a class       
   8     whose metaclass is metaMetaBunch) must define only class-scope data
   9     attributes (and possibly special methods, but NOT __init__and 
  10     __repr__!).  metaMetaBunch removes the data attributes from class
  11     scope, snuggles them instead as items in a class-scope dict named
  12     __dflts__, and puts in the class a __slots__with those attributes'
  13     names, an __init__that takes as optional keyword arguments each of
  14     them (using the values in __dflts__as defaults for missing ones), and
  15     a __repr__that shows the repr of each attribute that differs from its
  16     default value (the output of __repr__can be passed to __eval__to 
  17     make an equal instance, as per the usual convention in the matter, if
  18     each of the non-default-valued attributes respects the convention too)
  19     若一个class的元类为 metaMetaBunch, 那它就只允许在类定义中定义属性(也就是不允许在类外重新绑定属性),
  20     包括特殊属性(但不包括__init__和__new__!). metaMetaBunch 将会把类定义中的属性移到一个名叫 __dflts__的字典中去,
  21     并将这些属性名放到__slots__ 属性中保存.
  22     """
  23     def __new__(cls, classname, bases, classdict):
  24         """ Everything needs to be done in __new__, since type.__new__is
  25             where __slots__are taken into account.
  26         """
  27         # define as local functions the __init__and __repr__that we'll
  28         # use in the new class
  29         #定义我们在新类中会用到的__init__和__repr__方法
  30         def __init__(self, **kw):
  31             """ Simplistic __init__: first set all attributes to default
  32                 values, then override those explicitly passed in kw.
  33                 最简单的__init__函数: 首先将所有属性置为默认值,然后再将可选的用户定义值覆盖默认值
  34             """
  35             for k in self.__dflts__: setattr(self, k, self.__dflts__[k])
  36             for k in kw: setattr(self, k, kw[k])
  37         def __repr__(self):
  38             """ Clever __repr__: show only attributes that differ from the
  39                 respective default values, for compactness.
  40                聪明的 __repr__:非常简洁,它只显示与默认值不同的值,
  41             """
  42             rep = ['%s=%r' % (k, getattr(self, k)) for k in self.__dflts__
  43                     if getattr(self, k) != self.__dflts__[k]
  44                   ]
  45             return '%s(%s)' % (classname, ', '.join(rep))
  46         # build the newdict that we'll use as class-dict for the new class
  47         newdict = { '__slots__':[  ], '__dflts__':{  },
  48             '__init__':__init__, '__repr__':__repr__, }
  49         for k in classdict:
  50             if k.startswith('__') and k.endswith('__'):
  51                 # special methods: copy to newdict, warn about conflicts
  52                 if k in newdict:
  53                     warnings.warn("Can't set attr %r in bunch-class %r"
  54                         % (k, classname))
  55                 else:
  56                     newdict[k] = classdict[k]
  57             else:
  58                 # class variables, store name in __slots__, and name and
  59                 # value as an item in __dflts__
  60                 newdict['__slots__'].append(k)
  61                 newdict['__dflts__'][k] = classdict[k]
  62         # finally delegate the rest of the work to type.__new__
  63         return type.__new__(cls, classname, bases, newdict)
  64 class MetaBunch(object):
  65     """ For convenience: inheriting from MetaBunch can be used to get
  66         the new metaclass (same as defining __metaclass__yourself).
  67     """
  68     __metaclass__= metaMetaBunch
  69  
  70 
  71 class Point(MetaBunch):
  72     """ A point has x and y coordinates, defaulting to 0.0, and a color,
  73         defaulting to 'gray' -- and nothing more, except what Python and
  74         the metaclass conspire to add, such as __init__ and __repr__
  75         这是一个简单的点的元类: 
  76         除了Python和元类自动添加的 __init__ 和 __repr__方法之外,它只拥有坐标x,y及颜色color,默认值为(0,0,'gray')这三个属性
  77     """
  78     x = 0.0
  79     y = 0.0
  80     color = 'gray'
  81 # example uses of class Point
  82 q = Point(  )
  83 print q                     # prints: Point(  )
  84 p = Point(x=1.2, y=3.4)
  85 print p                     # prints: Point(y=3.399999999, x=1.2)

上面代码中的 print 语句 打印出了一个可读性很好的 Point 实例的字符串表示. Point 实例仍然是很精干的(省内存的),性能基本上与前面的Bunch class相同.(没有额外的隐式调用开销)

MetaClassInPython (last edited 2009-12-25 07:16:11 by localhost)