Python 中 Meta Classes详解


Posted in Python onFebruary 13, 2016

接触过 Django 的同学都应该十分熟悉它的 ORM 系统。对于 python 新手而言,这是一项几乎可以被称作“黑科技”的特性:只要你在models.py中随便定义一个Model的子类,Django 便可以:

  1. 获取它的字段定义,并转换成表结构
  2. 读取Meta内部类,并转化成相应的配置信息。对于特殊的Model(如abstract、proxy),还要进行相应的转换
  3. 为没有定义objects的Model加上一个默认的Manager

开发之余,我也曾脑补过其背后的原理。曾经,我认为是这样的:

启动时,遍历models.py中的所有属性,找到Model的子类,并对其进行上述的修改。
当初,我还以为自己触碰到了真理,并曾将其应用到实际生产中——为 SAE 的 KVDB 写了一个类 ORM 系统。然而在实现的过程中,我明显感受到了这种方法的丑陋,而且性能并不出色(因为要遍历所有的定义模块)。

那么事实上,Django 是怎么实现的呢?

自古以来我们制造东西的方法都是“自上而下”的,是用切削、分割、组合的方法来制造。然而,生命是自下而上地,自发地建造起来的,这个过程极为低廉。
——王晋康 《水星播种》

这句话揭示了生命的神奇所在:真正的生命都是由基本物质自发构成的,而非造物主流水线式的加工。

那么,如果 类 也有生命的话,对它自己的修饰就不应该由调用者来完成,而应该是自发的。

幸而,python 提供了造物主的接口——这便是 Meta Classes,或者称为“元类”。

元类 是什么?

简单说:元类就是类的类。

首先,要有一个概念:

python 中,一切都是对象。

没错,一切,包括 类 本身。

既然,类 是 对象,对象 是 类的实例,那么——类 也应该有 类 才对。

类的类:type

在 python 中,我们可以用type检测一个对象的类,如:

print type(1) # <type 'int'>

如果对一个类操作呢?

print type(int) # <type 'type'>

class MyClass(object): pass

print type(MyClass) # <type 'type'>

print type(type) # <type 'type'>

这说明:type其实是一个类型,所有类——包括type自己——的类都是type。

type 简介

从 官方文档 中,我们可以知道:

和 dict 类似,type 也是一个工厂构造函数,调用其将返回 一个type类型的实例(即 类)。
type 有两个重载版本:
+ `type(object)`,即我们最常用的版本。
+ `type(name, bases, dict)`,一个更强大的版本。通过指定 类名称(`name`)、父类列表(`bases`)和 属性字典(`dict`) 动态合成一个类。

下面两个语句等价:

class Integer(int):

  name = 'my integer'

  def increase(self, num):
    return num + 1

  # -------------------

  Integer = type('Integer', (int, ), {
  'name': 'my integer',
  'increase': lambda self, num: \
          num + 1  # 很酷的写法,不是么
  })

也就是说:类的定义过程,其实是type类型实例化的过程。

然而这和修饰一个已定义的类有什么关系呢?

当然有啦~既然“类的定义”就是“type类型的初始化过程”,那其中必定会调用到type的构造函数(__new__() 或 __init__())。只要我们继承 type类 并修改其 __new__函数,在这里面动手脚就可以啦。

接下来我们将通过一个栗子感受 python 的黑魔法,不过在此之前,我们要先了解一个语法糖。

__metaclass__ 属性

有没觉得上面第二段示例有些鬼畜呢?它勒令程序员将类的成员写成一个字典,简直是反人类。如果我们真的是要通过修改 元类 来改变 类 的行为的话,似乎就必须采用这种方法了~~简直可怕~~

好在,python 2.2 时引进了一个语法糖:__metaclass__。

class Integer(int):

  __metaclass__ = IntMeta

现在将会等价于:

Integer = IntMeta('Integer', (int, ), {})

由此一来,我们在使用传统类定义的同时,也可以使用元类啦。

栗子:子类净化器

需求描述

你是一个有语言洁癖的开发者,平时容不得别人讲一句脏话,在开发时也是如此。现在,你写出了一个非常棒的框架,并马上要将它公之于众了。不过,你的强迫症又犯了:如果你的使用者在代码中写满了脏话,怎么办?岂不是玷污了自己的纯洁?
假如你就是这个丧心病狂的开发者,你会怎么做?

在知道元类之前,你可能会无从下手。不过,这个问题你可以用 元类 轻松解决——只要在类定义时过滤掉不干净的字眼就好了(百度贴吧的干活~~)。

我们的元类看起来会是这样的:

sensitive_words_list = ['asshole', 'fuck', 'shit']

def detect_sensitive_words(string):
  '''检测敏感词汇'''
  words_detected = filter(lambda word: word in string.lower(), sensitive_words_list)

  if words_detected:
    raise NameError('Sensitive words {0} detected in the string "{1}".' \
      .format(
        ', '.join(map(lambda s: '"%s"' % s, words_detected)),
        string
      )
    )

class CleanerMeta(type):

  def __new__(cls, class_name, bases, attrs):
    detect_sensitive_words(class_name) # 检查类名
    map(detect_sensitive_words, attrs.iterkeys()) # 检查属性名

    print "Well done! You are a polite coder!" # 如无异常,输出祝贺消息

    return super(CleanerMeta, cls).__new__(cls, class_name, bases, attrs)
    # 重要!这行一定不能漏!!这回调用内建的类构造器来构造类,否则定义好的类将会变成 None
现在,只需这样定义基类:

class APIBase(object):

  __metaclass__ = CleanerMeta

  # ...
那么所有 APIBase 的派生类都会接受安全审查(奸笑~~):

class ImAGoodBoy(APIBase):

  a_polite_attribute = 1

# [Output] Well done! You are a polite coder!

class FuckMyBoss(APIBase):

  pass

# [Output] NameError: Sensitive words "fuck" detected in the string "FuckMyBoss".

class PretendToBePolite(APIBase):

  def __fuck_your_asshole(self):
    pass

# [Output] NameError: Sensitive words "asshole", "fuck" detected in the string "_PretendToBePolite__fuck_your_asshole".

看,即使像最后一个例子中的私有属性也难逃审查,因为它们本质都是相同的。

甚至,你还可以对有问题的属性进行偷偷的修改,比如 让不文明的函数在调用时打出一行警告 等等,这里就不多说了。

元类 在实际开发中的应用

日常开发时,元类 常用吗?

当然,Django 的 ORM 就是一个例子,大名鼎鼎的 SQLAlchemy 也用了这种黑魔法。

此外,在一些小型的库中,也有 元类 的身影。比如 abc(奇怪的名字~~)——这是 python 的一个内建库,用于模拟 抽象基类(Abstract Base Classes)。开发者可以使用 abc.abstractmethod 装饰器,将 指定了 __metaclass__ = abc.ABCMeta 的类的方法定义成 抽象方法,同时这个类也成了 抽象基类,抽象基类是不可实例化的。这便实现了对 抽象基类 的模拟。

倘若你也有需要动态修改类定义的需求,不妨也试试这种“黑魔法”。

小结

  1. 类 也是 对象,所有的类都是type的实例
  2. 元类(Meta Classes)是类的类
  3. __metaclass__ = Meta 是 Meta(name, bases, dict) 的 语法糖
  4. 可以通过重载元类的 __new__ 方法,修改 类定义 的行为
Python 相关文章推荐
基于使用paramiko执行远程linux主机命令(详解)
Oct 16 Python
Python使用matplotlib实现绘制自定义图形功能示例
Jan 18 Python
Python实现扣除个人税后的工资计算器示例
Mar 26 Python
Django中如何防范CSRF跨站点请求伪造攻击的实现
Apr 28 Python
pycharm访问mysql数据库的方法步骤
Jun 18 Python
wxPython绘图模块wxPyPlot实现数据可视化
Nov 19 Python
python pptx复制指定页的ppt教程
Feb 14 Python
Selenium基于PIL实现拼接滚动截图
Apr 10 Python
Python如何实现大型数组运算(使用NumPy)
Jul 24 Python
Python unittest如何生成HTMLTestRunner模块
Sep 08 Python
Python基础之元类详解
Apr 29 Python
Python中X[:,0]和X[:,1]的用法
May 10 Python
教大家使用Python SqlAlchemy
Feb 12 #Python
理解Python垃圾回收机制
Feb 12 #Python
一步步解析Python斗牛游戏的概率
Feb 12 #Python
常用python编程模板汇总
Feb 12 #Python
python黑魔法之参数传递
Feb 12 #Python
python实现井字棋游戏
Mar 30 #Python
python搭建微信公众平台
Feb 09 #Python
You might like
php基础知识:类与对象(5) static
2006/12/13 PHP
php简单解析mysqli查询结果的方法(2种方法)
2016/06/29 PHP
javascript 进阶篇2 CSS XML学习
2012/03/14 Javascript
jQuery实现复选框全选/取消全选/反选及获得选择的值
2014/06/12 Javascript
jQuery实现的向下图文信息滚动效果
2015/05/03 Javascript
JavaScript数组对象实现增加一个返回随机元素的方法
2015/07/27 Javascript
理解和运用JavaScript的闭包机制
2015/08/13 Javascript
jQuery实现的Tab滑动选项卡及图片切换(多种效果)小结
2015/09/14 Javascript
详解js的事件代理(委托)
2016/12/22 Javascript
js实现添加删除表格(两种方法)
2017/04/27 Javascript
Node.JS循环删除非空文件夹及子目录下的所有文件
2018/03/12 Javascript
javascript设计模式 ? 外观模式原理与用法实例分析
2020/04/15 Javascript
详解如何在vue+element-ui的项目中封装dialog组件
2020/12/11 Vue.js
js制作提示框插件
2020/12/24 Javascript
[10:21]DOTA2-DPC中国联赛 正赛 PSG.LGD vs Aster 选手采访
2021/03/11 DOTA
Python程序设计入门(2)变量类型简介
2014/06/16 Python
python获取图片颜色信息的方法
2015/03/18 Python
Python语言描述连续子数组的最大和
2018/01/04 Python
Python基于辗转相除法求解最大公约数的方法示例
2018/04/04 Python
tensorflow实现在函数中用tf.Print输出中间值
2020/01/21 Python
Python3 利用face_recognition实现人脸识别的方法
2020/03/13 Python
如何基于python对接钉钉并获取access_token
2020/04/21 Python
使用matplotlib动态刷新指定曲线实例
2020/04/23 Python
聊聊python中的循环遍历
2020/09/07 Python
利用python如何实现猫捉老鼠小游戏
2020/12/04 Python
使用Html5 Stream开发实时监控系统
2020/06/02 HTML / CSS
西班牙著名的珠宝首饰品牌:P D PAOLA
2018/09/15 全球购物
法国发饰品牌:Alexandre De Paris
2018/12/04 全球购物
IdealFit官方网站:女性蛋白质、补充剂和运动服装
2019/03/24 全球购物
小学生自我鉴定
2013/10/12 职场文书
党校培训思想汇报
2013/12/30 职场文书
事业单位绩效考核实施方案
2014/03/27 职场文书
本科生导师推荐信范文
2014/05/18 职场文书
赢在执行观后感
2015/06/16 职场文书
python基础之while循环语句的使用
2021/04/20 Python
Django路由层如何获取正确的url
2021/07/15 Python