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 相关文章推荐
在python的类中动态添加属性与生成对象
Sep 17 Python
基于并发服务器几种实现方法(总结)
Dec 29 Python
Python实用技巧之列表、字典、集合中根据条件筛选数据详解
Jul 11 Python
浅谈python脚本设置运行参数的方法
Dec 03 Python
python爬虫URL重试机制的实现方法(python2.7以及python3.5)
Dec 18 Python
Python登录系统界面实现详解
Jun 25 Python
python 标准差计算的实现(std)
Jul 29 Python
python 检查数据中是否有缺失值,删除缺失值的方式
Dec 02 Python
python实现全排列代码(回溯、深度优先搜索)
Feb 26 Python
pytorch随机采样操作SubsetRandomSampler()
Jul 07 Python
使用Python将语音转换为文本的方法
Aug 10 Python
python实现进度条的多种实现
Apr 29 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 数据结构 算法 三元组 Triplet
2011/07/02 PHP
php 判断访客是否为搜索引擎蜘蛛的函数代码
2011/07/29 PHP
UTF-8正则表达式如何匹配汉字
2015/08/03 PHP
在第一个input框内输入内容.textarea自动得到第一个文件框的值的javascript代码
2007/04/20 Javascript
jquery 屏蔽一个区域内的所有元素,禁止输入
2009/10/22 Javascript
JS代码同步文本框内容的实例方法
2013/07/12 Javascript
关闭ie窗口清除Session的解决方法
2014/01/10 Javascript
在JavaScript应用中使用RequireJS来实现延迟加载
2015/07/01 Javascript
javascript实现将文件保存到本地方法汇总
2015/07/26 Javascript
javascript函数式编程程序员的工具集
2015/10/11 Javascript
JavaScript数组的一些奇葩行为
2016/01/25 Javascript
3分钟快速搭建nodejs本地服务器方法运行测试html/js
2017/04/01 NodeJs
详解Nodejs内存治理
2018/05/13 NodeJs
微信小程序实现电子签名并导出图片
2020/05/27 Javascript
vue 使用class创建和清除水印的示例代码
2020/12/25 Vue.js
基于Vue3.0开发轻量级手机端弹框组件V3Popup的场景分析
2020/12/30 Vue.js
[01:07:19]DOTA2-DPC中国联赛 正赛 CDEC vs XG BO3 第一场 1月19日
2021/03/11 DOTA
Python中的日期时间处理详解
2016/11/17 Python
python rsa 加密解密
2017/03/20 Python
python中类变量与成员变量的使用注意点总结
2017/04/29 Python
opencv python 傅里叶变换的使用
2018/07/21 Python
python实现点对点聊天程序
2018/07/28 Python
使用Python实现微信提醒备忘录功能
2018/12/04 Python
python实现QQ批量登录功能
2019/06/19 Python
Django 过滤器汇总及自定义过滤器使用详解
2019/07/19 Python
基于keras中的回调函数用法说明
2020/06/17 Python
浅谈Html5页面打开app的一些思考
2020/03/30 HTML / CSS
意大利奢侈品网站:Italist
2016/08/23 全球购物
购买英国原创艺术:Art Gallery
2018/08/25 全球购物
三星印度官网:Samsung印度
2019/08/03 全球购物
税务专业毕业生自荐信
2013/11/10 职场文书
初中军训感想300字
2014/03/05 职场文书
英语系毕业生求职信
2014/07/13 职场文书
税务干部群众路线教育实践活动自我剖析材料
2014/09/21 职场文书
党员个人剖析材料
2014/09/30 职场文书
追悼会悼词大全
2015/06/23 职场文书