“元类的魔幻变化比 99% 的用户所担心的更多,当你搞不懂是否真的需要用它的时候,就是不需要。”

—Tim Peters

本文源于在 PyCon UK 2008 上的一个快速演讲。

元类被称为 Python 中的“深奥的巫术”。尽管你需要用到它的地方极少(除非你基于zope 编程),可事实上它的基础理论其实令人惊讶地易懂。

一切皆对象

  • 一切皆对象
  • 一切都有类型
  • “class”和“type”之间本质上并无不同
  • 类也是对象
  • 它们的类型是 type

以前,术语 type 用于内置类型,而术语 class 用于用户定义的类,但自 Pythoon 2.2 以来“class”和“type”本质上并无不同。

对于旧风格(old-style)类的类型是 types.ClassType。

真的,这是真的

Python 2.5.1 (r251:54869, Apr 18 2007, 22:08:04)
>>> class Something(object):
...     pass
...
>>> Something
<class '__main__.Something'>
>>> type(Something)
<type 'type'>

从这里可以看出在交互式解释器中创建的类是一个 first class 的对象。

类的类是……

它的元类……

就像对象是类的实例一样,类是它的元类的实例。

调用元类可以创建类。

确切来说,Python 中的其它对象也是如此。

因此当你创建一个类时……

解释器会调用元类来生成它……

定义一个继承自 object 的普通类意味着调用 type 来创建它:

>>> help(type)

Help on class type in module __builtin__:

class type(object)

|  type(object) -> the object's type

|  type(name, bases, dict) -> a new type

type 的第二种用法尤为重要。当 Python 解释器在执行一条类定义语句时(如例子中最初的两行代码之后),它会用下面的参数调用 type:

  • 字符串形式的类名
  • 元组形式的基类序列——在我们的例子中是只有一个元素的元组(’one-pl’)[1],如(object,)。
  • 包括由名字影射的类成员(类属性、方法等)的字典

简单模拟

>>> def __init__(self):
...     self.message = 'Hello World'
...
>>> def say_hello(self):
...     print self.message
...
>>> attrs = {'__init__': __init__, 'say_hello': say_hello}
>>> bases = (object,)
>>> Hello = type('Hello', bases, attrs)
>>> Hello
<class '__main__.Hello'>
>>> h = Hello()
>>> h.say_hello()
Hello World

以上代码创建了类属性的字典,然后调用 type 来创建了名为 Hello 的类。

__metaclass__ 的魔法

只要在类定义中把 __metaclass__ 设置为任意有着与 type 相同参数的可调用对象,就能够提供自定义的元类。

通常使用从 type 继承的方法:

class PointlessMetaclass(type):
    def __new__(meta, name, bases, attrs):
        # do stuff...
        return type.__new__(meta, name, bases, attrs)

重要的是在 __new__ 方法中我们能够读取或改变传入的用以创建新类的参数。从而能够内省属性字典和改动、增加或者删除成员。

尽管当实例化一个类时这两个函数都会被调用,但覆盖 __new__ 比 __init__ 更为重要。__init__ 初始化一个实例,而 __new__ 的职责是创建它。因此如果元类用以自定义类的创建,就需要覆盖 type 的 __new__。

使用新类而非仅仅提供工厂函数的原因在于如果使用工厂函数(那样只是调用 type)的话元类不会被继承。

In Action...

>>> class WhizzBang(object):
...     __metaclass__ = PointlessMetaclass
...
>>> WhizzBang
<class '__main__.WhizzBang'>
>>> type(WhizzBang)
<class '__main__.PointlessMetaClass'>

WhizzBang 是一个类,但它现在已经不是 type 的实例,而是我们自定义的元类的实例了……

这有什么用?

很好的问题,元类将用在创建使用了它的新类时调用,这里是一些关于这样做的好处的观点:

  • 装饰(Decorate)类的所有方法,用以日志记录或者性能剖分。
  • 自动 Mix-in 新方法
  • 在创建时注册类。(例如自动注册插件或从类成员创建数据库模式。)
  • 提供接口注册,功能自动发现和接口适配。
  • 类校验:防止子类化,校验所有的方法是否都有 docstrings。

最重要之处在于元类中是在最后对 type 的调用时才真正创建类,所以可以自由地随你喜欢地改变属性字典(以及名称和元组形式的基类序列)。

一些流行的 Python ORM(Object Relational Mappers(对象关系影射),用以和数据库协同工作)也如此使用元类。

哦,还有因为元类是继承的,所以你能够提供一个使用了你的元类的基类,而继承自它的子类就无需显式声明它了。

但是……

我曾未需要使用它来编写代码……(我们用它来剖分,也在 Ironclad 项目广泛应用它,但我不编写这些)。

还有,这一切只适用于 Python 2.x,其中的机制在 Python 3 中已经改变了。

type(type) is type

在 Python 2.6 中现在也可用使用  class decorators 来实现许多以前可能需要用元类来实现的东西。

最后,还有一个极尽奇技淫巧的例子(稍为深入,但仍然不难消化),可以去看看 The Selfless Metaclass。它通过字节码和方法签名重写来避免显式地声明 self 。

[1]

'one-pl'是指只有一个元素的元组。

Python——五分钟理解元类(metaclasses)的更多相关文章

  1. Python——五分钟理解函数式编程与闭包

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是Python专题的第9篇文章,我们来聊聊Python的函数式编程与闭包. 函数式编程 函数式编程这个概念我们可能或多或少都听说过,刚听 ...

  2. python类:描述器Descriptors和元类MetaClasses

    http://blog.csdn.net/pipisorry/article/details/50444769 描述器(Descriptors) 描述器决定了对象属性是如何被访问的.描述器的作用是定制 ...

  3. Python 元类 - Metaclasses

    Python 元类 - Metaclasses 默认情况下儿, classes 是有 type() 构造的. 类的结构体在一个新的 namespace 被执行, 类的名字 class name 绑定( ...

  4. python 面向对象进阶之元类metaclass

    一:知识储备 exec exec:三个参数 参数一:字符串形式的命令 参数二:全局作用域(字典形式),如果不指定,默认为globals() 参数三:局部作用域(字典形式),如果不指定,默认为local ...

  5. 神级程序员通过两句话带你完全掌握Python最难知识点——元类!

    千万不要被所谓"元类是99%的python程序员不会用到的特性"这类的说辞吓住.因为 每个中国人,都是天生的元类使用者 学懂元类,你只需要知道两句话: 道生一,一生二,二生三,三生 ...

  6. 廖雪峰Python学习笔记——使用元类

    元类(MetaClasses) 元类提供了一个改变Python类行为的有效方式. 元类的定义是“一个类的类”.任何实例是它自己的类都是元类. class demo(object): pass obj ...

  7. Python-深入理解元类(metaclass)

    1.使用 type 动态创建类(type 是一个类, 用来创建类对象的元类, 所以也可以继承) type("Person", (), {"name": &quo ...

  8. python 面向对象十二 元类

    一.类也是对象 只要使用关键字class,Python解释器在执行的时候就会创建一个对象.下面的代码段: class ObjectCreator(object): pass 将在内存中创建一个对象,名 ...

  9. Python进阶——详解元类,metaclass的原理和用法

    本文始发于个人公众号:TechFlow,原创不易,求个关注 今天是Python专题第18篇文章,我们来继续聊聊Python当中的元类. 在上上篇文章当中我们介绍了type元类的用法,在上一篇文章当中我 ...

随机推荐

  1. 多线程调用COM组件的体会(CoInitialize)(转)

    原文转自 https://www.cnblogs.com/manors/archive/2010/05/17/COM_Initialize_STA_MTA.html 与设备打交道时,我们经常用到COM ...

  2. linux 内核库函数 【转】

    转自:http://blog.chinaunix.net/uid-20321537-id-1966892.html 当编写驱动程序时,一般情况下不能使用C标准库的函数.Linux内核也提供了与标准库函 ...

  3. Segment Tree

    姑且叫这种数据结构这个名字 #include<iostream> #include<cstdio> #define N 200005 #define Lson ret<& ...

  4. 可视化web日志分析工具Logstalgia

    https://blog.csdn.net/zrools/article/details/47250661

  5. (21)C#VS快捷键

    1.移动光标 ctrl+ 右键:按单词移动 home:移动到一行得开头 ,end:移动到一行得末尾 ctrl+home:移动到文本得第一行初始位置,ctrl+end文本最末尾 pageDn:往文件下面 ...

  6. 最小生成树 (Minimum Spanning Tree,MST) --- Kruskal算法

    本文链接:http://www.cnblogs.com/Ash-ly/p/5409265.html 引导问题: 假设要在N个城市之间建立通信联络网,则连通N个城市只需要N - 1条线路.这时,自然会考 ...

  7. java.sql.SQLException: Access denied for user 'roo'@'localhost' (using password: YES)

    初学mysql,安装了mysql8.0.11,激动的用jdbc连接数据库,出现error,折腾了三天依旧无解,最后无奈装了比较稳定的mysql5.5,问题得以解决,很迷,但只要error没了就开心. ...

  8. Sql Jions 的简易理解

    Sql Jions 的简易理解 Select  * from TableA A  left jion TableB  B on  A.key = B.key Select  * from TableA ...

  9. [POI2014]Salad Bar

    题目大意: 一个长度为$n(n\leq10^6)$的字符串,每一位只会是$p$或$j$.你需要取出一个子串$S$(从左到右或从右到左一个一个取出),使得不管是从左往右还是从右往左取,都保证每时每刻已取 ...

  10. requestFeature() must be called before adding content

    错误:requestFeature() must be called before adding content 原因: Android去掉标题栏点菜单键程序崩溃 在安卓以前的版本中,我们会使用以下两 ...