Python探索之Metaclass初步了解


Posted in Python onOctober 28, 2017

先以一个大牛的一段关于Python Metapgramming的著名的话来做开头:

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

翻译一下:Metaclasses是99%的用户都无需费神的黑科技。如果你还在纠结你是不是需要它的话,答案是NO (真正需要的人根本不需要解释) ? Tim Peters

这是什么鬼话?道可道,非常道吗?

Meta?

好,装B已毕。这确实是一个冷僻的,不常用的话题。一篇短文肯定讲不完。 所以叫做初步了解。

python中的类

首先这里讨论的python类,都基于继承于object的新式类进行讨论。

首先在python中,所有东西都是对象。这句话非常重要要理解元类我要重新来理解一下python中的类

class Trick(object):
  pass

当python在执行带class语句的时候,会初始化一个类对象放在内存里面。例如这里会初始化一个Trick对象

这个对象(类)自身拥有创建对象(通常我们说的实例,但是在python中还是对象)的能力。

为了方便后续理解,我们可以先尝试一下在新式类中最古老厉害的关键字type。

input:
class Trick(object):
pass
print type('123')
print type(123)
print type(Trick())
output:
<type 'str'>
<type 'int'>
<class '__main__.Trick'>

可以看到能得到我们平时使用的 str, int, 以及我们初始化的一个实例对象Trick()

但是下面的方法你可能没有见过,type同样可以用来动态创建一个类

type(类名, 父类的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))

英文meta这个词其实是从希腊语里面借来的。wikipedia上的解释是:

indicate a concept which is an abstraction behind another concept, used to complete or add to the latter

不看还好,其实看了更晕。好在后面的解释有一句“更高一层的抽象”,可以帮助理解。 其实我们可以这样理解。meta的意思就是“关于什么的什么”:比如metadata可以理解为“关于数据的数据”,metaprogramming可以理解为“关于编程的编程”。这就和“更高一层的抽象” 比较契合了。同时又隐隐和编程中的另一个永恒主题-recursion联系在了一起。

另外,meta这个词天朝这边翻译成“元”,海峡对岸翻译成“后设”。其实我都不大理解从何而来。

元类一般用于创建类。在执行类定义时,解释器必须要知道这个类的正确的元类。解释器会先寻找类属性__metaclass__,如果此属性存在,就将这个属性赋值给此类作为它的元类。如果此属性没有定义,它会向上查找父类中的__metaclass__.如果还没有发现__metaclass__属性,解释器会检查名字为__metaclass__的全局变量,如果它存在,就使用它作为元类。否则, 这个类就是一个传统类,并用 types.ClassType 作为此类的元类。

在执行类定义的时候,将检查此类正确的(一般是默认的)元类,元类(通常)传递三个参数(到构造器): 类名,从基类继承数据的元组,和(类的)属性字典。

实例

聚焦到我们今天的主题,metaprogramming就是编写用来生成代码的代码。

假设我们写了一个NB的函数,用来计算一个任意复杂的算数表达式的值:

像1+2, 3*6+10, 什么的都可以交给它去计算。这样的函数的算法不是我们的主题,所以我们请出python自带的大招eval(),一行就可以搞定了:

def calc(expression):
  return eval(expression)

因为输入的可能性是无限的,所以我们肯定要好好测试一下这个函数了。假定我们想了 上百个test case。又假定我们是用unittest这个module来做测试的。这样的测试程序一般会长成这样:

import unittest
class TestStringMethods(unittest.TestCase):
  def test_upper(self):
    self.assertEqual('foo'.upper(), 'FOO')
  def test_isupper(self):
    self.assertTrue('FOO'.isupper())
    self.assertFalse('Foo'.isupper())
  def test_split(self):
    s = 'hello world'
    self.assertEqual(s.split(), ['hello', 'world'])
    # check that s.split fails when the separator is not a string
    with self.assertRaises(TypeError):
      s.split(2)
if __name__ == '__main__':
  unittest.main()

所以我们的目的就是用metaprogramming的方式来自动产生类似上面的测试类。

先上程序后解释:

#!/usr/bin/python3
import unittest
def calc(expression):
  return eval(expression)
def add_test(name, asserts):
  def test_method(asserts):
    def fn(self):
      left, right = asserts.split('=')
      expected = str(calc(left))
      self.assertEqual(expected, right)
    return fn
  d = {'test1': test_method(asserts)}
  cls = type(name, (unittest.TestCase,), d)
  globals()[name] = cls
if __name__ == '__main__':
  for i, t in enumerate([
      "1+2=3",
      "3*5*6=90"]):
    add_test("Test%d" % i, t)
  unittest.main()

NB的calc()函数我们解释过了。main这段也比较简单:我们用声明的方式定义了一组测试,然后通过unittest来执行。

有点复杂的是add_test()。我们先来看看最内层的fn(self)这个方法。逻辑上,它就是把输入的测试用例分成两份,一份是calc()的输入,一份是我们期待的结果;然后调用calc(), 接着用assertEqual()来测试。

但是这个self有点奇怪 ? 这里没有类,哪里来的self? 其实fn(self)确实是一个类的方法,只不过这个类是我们通过代码动态生成的。也就是下面这一行:

cls = type(name, (unittest.TestCase,), d)

这里的type()就是通常我们用来检查某个变量的类型的那个函数。只不过它还有另外一种不大为人知的形式:

class type(name, bases, dict)

这第二种形式,就会产生一个新的类型。以我们的程序为例,就是以unit.TestCase为baseclass, 产生了一个名为TestN的新类型,改类型的实现由d给出,而d就包含了通过closure返回的fn(self)这个方法。只不过在这个新类里面,它的名字叫做 test1()。

最后,我们把这个新产生的类加入到当前全局符号表里面,也就相当于上面给出的unittest的例子。

所以,总结一下。当我们运行这个脚本的时候,这段比较短的代码会针对每一个测试的表达式产生一个新的测试类,并动态生成测试的方法加载到该类里面。unitest从globals中找到这些类并一一执行测试。

上面的例子中,其实一行一行手打calc(1+2) == 3也没什么大不了的。但是当你要表达的逻辑比较复杂的时候,metaprogramming的强大就体现出来了。

总结

那么,看完这篇文章,我们也成为Tim所说的1%的程序猿了!其实,也许他的意思是,99%的编程工作都用不到这样技巧。在一些特殊的场合,比如编写某种框架的时候,metaprogramming会做到事半功倍。祝你在实践中碰到这样的机会。

以上就是本文关于Python探索之Metaclass初步了解的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站:Python编程之Re模块下的函数介绍、python中模块的__all__属性详解等,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!

Python 相关文章推荐
举例讲解Python中的list列表数据结构用法
Mar 12 Python
详解python进行mp3格式判断
Dec 23 Python
利用python实现命令行有道词典的方法示例
Jan 31 Python
Python编程之字符串模板(Template)用法实例分析
Jul 22 Python
Django原生sql也能使用Paginator分页的示例代码
Nov 15 Python
对numpy的array和python中自带的list之间相互转化详解
Apr 13 Python
修改默认的pip版本为对应python2.7的方法
Nov 06 Python
使用python Telnet远程登录执行程序的方法
Jan 26 Python
python 中xpath爬虫实例详解
Aug 26 Python
python实现ssh及sftp功能(实例代码)
Mar 16 Python
PyInstaller运行原理及常用操作详解
Jun 13 Python
pycharm 使用anaconda为默认环境的操作
Feb 05 Python
Python编程之Re模块下的函数介绍
Oct 28 #Python
Python探索之静态方法和类方法的区别详解
Oct 27 #Python
Python探索之爬取电商售卖信息代码示例
Oct 27 #Python
Python 列表理解及使用方法
Oct 27 #Python
Python算法之求n个节点不同二叉树个数
Oct 27 #Python
Python探索之自定义实现线程池
Oct 27 #Python
python音频处理用到的操作的示例代码
Oct 27 #Python
You might like
PHP 读取和修改大文件的某行内容的代码
2009/10/30 PHP
php设计模式之单例模式实例分析
2015/02/25 PHP
thinkPHP中create方法与令牌验证实例浅析
2015/12/08 PHP
PHP CURL中传递cookie的方法步骤
2019/05/09 PHP
Laravel5.4简单实现app接口Api Token认证方法
2019/08/29 PHP
JavaScript中的一些定位属性[图解]
2010/07/14 Javascript
Chosen 基于jquery的选择框插件使用方法
2012/05/30 Javascript
无缝滚动改进版支持上下左右滚动(封装成函数)
2012/12/04 Javascript
jQuery.fn和jQuery.prototype区别介绍
2013/10/05 Javascript
javascript中普通函数的使用介绍
2013/12/19 Javascript
easyui datagrid 键盘上下控制选中行示例
2014/03/31 Javascript
jQuery搜索同辈元素方法
2015/02/10 Javascript
jQuery与JavaScript节点创建方法的对比
2016/11/18 Javascript
那些精彩的JavaScript代码片段
2017/01/12 Javascript
原生JS实现移动端web轮播图详解(结合Tween算法造轮子)
2017/09/10 Javascript
Js利用console计算代码运行时间的方法示例
2017/09/24 Javascript
javascript基于定时器实现进度条功能实例
2017/10/13 Javascript
10 种最常见的 Javascript 错误(频率最高)
2018/02/08 Javascript
vue项目开发中setTimeout等定时器的管理问题
2018/09/13 Javascript
webpack配置proxyTable时pathRewrite无效的解决方法
2018/12/13 Javascript
微信小程序实现多个按钮的颜色状态转换
2019/02/15 Javascript
解决layer.prompt无效的问题
2019/09/24 Javascript
[52:40]完美世界DOTA2联赛PWL S2 Magma vs GXR 第一场 11.29
2020/12/02 DOTA
python关键字and和or用法实例
2015/05/28 Python
python简单实现操作Mysql数据库
2018/01/29 Python
Python将8位的图片转为24位的图片实现方法
2018/10/24 Python
python找出因数与质因数的方法
2019/07/25 Python
Python sklearn库实现PCA教程(以鸢尾花分类为例)
2020/02/24 Python
5个你不知道的HTML5的接口介绍
2013/08/07 HTML / CSS
html5实现多图片预览上传及点击可拖拽控件
2018/03/15 HTML / CSS
外贸业务员求职自荐信分享
2013/09/21 职场文书
好人好事演讲稿
2014/09/01 职场文书
铅球加油稿100字
2014/09/26 职场文书
个人学习党的群众路线教育实践活动心得体会
2014/11/05 职场文书
2016年优秀班主任先进事迹材料
2016/02/26 职场文书
创业计划书之家教中心
2019/09/25 职场文书