Python 类与元类的深度挖掘 I【经验】


Posted in Python onMay 06, 2016

上一篇介绍了 Python 枚举类型的标准库,除了考虑到其实用性,还有一个重要的原因是其实现过程是一个非常好的学习、理解 Python 类与元类的例子。因此接下来两篇就以此为例,深入挖掘 Python 中类与元类背后的机制。

翻开任何一本 Python 教程,你一定可以在某个位置看到下面这两句话:

Python 中一切皆为对象(Everything in Python is an object);

Python 是一种面向对象编程(Object Oriented Programming, OOP)的语言。

虽然在上面两句话的语境中,对象(Object)的含义可能稍有不同,但可以肯定的是对象在 Python 中具有非常重要的意义,也是我们接下来将要讨论的所有内容的基础。那么,对象到底是什么?

对象(Object)

对象是 Python 中对数据的一种抽象,Python 程序中所有数据都是通过对象或对象之间的关系来表示的。[ref: Data Model]

港台将 Object 翻译为“物件”,可以将其看作是一个盛有数据的盒子,只不过除了纯粹的数据之外还有其它有用的属性信息,在 Python 中,所有的对象都具有id、type、value三个属性:

+---------------+
|  |
| Python Object |
|  |
+------+--------+
| ID | |
+---------------+
| Type | |
+---------------+
| Value| |
+---------------+

其中 id 代表内存地址,可以通过内置函数 id() 查看,而 type 表示对象的类别,不同的类别意味着该对象拥有的属性和方法等,可以通过 type() 方法查看:

 def who(obj):

print(id(obj), type(obj))


who(1)


who(None)


who(who)


4515088368 


4514812344 


4542646064

对象作为 Python 中的基本单位,可以被创建、命名或删除。Python 中一般不需要手动删除对象,其垃圾回收机制会自动处理不再使用的对象,当然如果需要,也可以使用 del 语句删除某个变量;所谓命名则是指给对象贴上一个名字标签,方便使用,也就是声明或赋值变量;接下来我们重点来看如何创建一个对象。对于一些 Python 内置类型的对象,通常可以使用特定的语法生成,例如数字直接使用阿拉伯数字字面量,字符串使用引号 '',列表使用 [],字典使用 {} ,函数使用 def 语法等,这些对象的类型都是 Python 内置的,那我们能不能创建其它类型的对象呢?

类与实例

既然说 Python 是面向对象编程语言,也就允许用户自己创建对象,通常使用 class 语句,与其它对象不同的是,class 定义的对象(称之为类)可以用于产生新的对象(称之为实例):

class A:

pass


a = A()


who(A)


who(a)


140477703944616 


4542635424

上面的例子中 A 是我们创建的一个新的类,而通过调用 A() 可以获得一个 A 类型的实例对象,我们将其赋值为 a,也就是说我们成功创建了一个与所有内置对象类型不同的对象 a,它的类型为 __main__.A!至此我们可以将 Python 中一切的对象分为两种:

可以用来生成新对象的类,包括内置的 int、str 以及自己定义的 A 等;

由类生成的实例对象,包括内置类型的数字、字符串以及自己定义的类型为 __main__.A 的 a。

单纯从概念上理解这两种对象没有任何问题,但是这里要讨论的是在实践中不得不考虑的一些细节性问题:

需要一些方便的机制来实现面向对象编程中的继承、重载等特性;

需要一些固定的流程让我们可以在生成实例化对象的过程中执行一些特定的操作;

这两个问题主要关于类的一些特殊的操作,也就是这一篇后面的主要内容。如果再回顾一下开头提到的两句话,你可能会想到,既然类本身也是对象,那它们又是怎样生成的?这就是后一篇将主要讨论的问题:用于生成类对象的类,即元类(Metaclass)。

super, mro()

0x00 Python 之禅中提到的最后一条,命名空间(namespace)是个绝妙的理念,类或对象在 Python 中就承担了一部分命名空间的作用。比如说某些特定的方法或属性只有特定类型的对象才有,不同类型对象的属性和方法尽管名字可能相同,但由于隶属不同的命名空间,其值可能完全不同。在实现类的继承与重载等特性时同样需要考虑命名空间的问题,以枚举类型的实现为例,我们需要保证枚举对象的属性名称不能有重复,因此我们需要继承内置的 dict 类:

 

 class _EnumDict(dict):

def __init__(self):


dict.__init__(self)


self._member_names = []


def keys(self):


keys = dict.keys(self)


return list(filter(lambda k: k.isupper(), keys))


ed = _EnumDict()


ed['RED'] = 1


ed['red'] = 2


print(ed, ed.keys())


{'RED': 1, 'red': 2} ['RED']

在上面的例子中 _EnumDict 重载同时调用了父类 dict 的一些方法,上面的写法在语法上是没有错误的,但是如果我们要改变 _EnumDict 的父类,不再是继承自 dict,则必须手动修改所有方法中 dict.method(self) 的调用形式,这样就不是一个好的实践方案了。为了解决这一问题,Python 提供了一个内置函数 super():

print(super.__doc__)

super() -> same as super(__class__, )


super(type) -> unbound super object


super(type, obj) -> bound super object; requires isinstance(obj, type)


super(type, type2) -> bound super object; requires issubclass(type2, type)


Typical use to call a cooperative superclass method:


class C(B):


def meth(self, arg):


super().meth(arg)


This works for class methods too:


class C(B):


@classmethod


def cmeth(cls, arg):


super().cmeth(arg)

我最初只是把 super() 当做指向父类对象的指针,但实际上它可以提供更多功能:给定一个对象及其子类(这里对象要求至少是类对象,而子类可以是实例对象),从该对象父类的命名空间开始搜索对应的方法。

以下面的代码为例:

 class A(object):

def method(self):


who(self)


print("A.method")


class B(A):


def method(self):


who(self)


print("B.method")


class C(B):


def method(self):


who(self)


print("C.method")


class D(C):


def __init__(self):


super().method()


super(__class__, self).method()


super(C, self).method() # calling C's parent's method


super(B, self).method() # calling B's parent's method


super(B, C()).method() # calling B's parent's method with instance of C


d = D()


print("\nInstance of D:")


who(d)


4542787992 


C.method


4542787992 


C.method


4542787992 


B.method


4542787992 


A.method


4542788048 


A.method


Instance of D:


4542787992

当然我们也可以在外部使用 super() 方法,只是不能再用缺省参数的形式,因为在外部的命名空间中不再存在 __class__ self:

 super(D, d).method() # calling D's parent's method with instance d

4542787992 


C.method

  上面的例子可以用下图来描述:

+----------+
| A |
+----------+
| method() <---------------+ super(B,self)
+----------+  |
    |
+----------+  +----------+
| B |  | D |
+----------+ super(C,self) +----------+
| method() <---------------+ method() |
+----------+  +----------+
    |
+----------+  |
| C |  |
+----------+  | super(D,self)
| method() <---------------+
+----------+

 可以认为 super() 方法通过向父类方向回溯给我们找到了变量搜寻的起点,但是这个回溯的顺序是如何确定的呢?上面的例子中继承关系是 object->A->B->C->D 的顺序,如果是比较复杂的继承关系呢?

 

 class A(object):

pass


class B(A):


def method(self):


print("B's method")


class C(A):


def method(self):


print("C's method")


class D(B, C):


def __init__(self):


super().method()


class E(C, B):


def __init__(self):


super().method()


d = D()


e = E()


B's method


C's method

Python 中提供了一个类方法 mro() 可以指定搜寻的顺序,mro 是Method Resolution Order 的缩写,它是类方法而不是实例方法,可以通过重载 mro() 方法改变继承中的方法解析顺序,但这需要在元类中完成,在这里只看一下其结果:

 D.mro()

[__main__.D, __main__.B, __main__.C, __main__.A, object]


E.mro()


[__main__.E, __main__.C, __main__.B, __main__.A, object]


super() 方法就是沿着 mro() 给出的顺序向上寻找起点的:


super(D, d).method()


super(E, e).method()


B's method


C's method


super(C, e).method()


super(B, d).method()


B's method


C's method
Python 相关文章推荐
Python编写屏幕截图程序方法
Feb 18 Python
python使用Image处理图片常用技巧分析
Jun 01 Python
Django分页查询并返回jsons数据(中文乱码解决方法)
Aug 02 Python
Python推导式简单示例【列表推导式、字典推导式与集合推导式】
Dec 04 Python
使用Python OpenCV为CNN增加图像样本的实现
Jun 10 Python
Python实现的ftp服务器功能详解【附源码下载】
Jun 26 Python
安装Pycharm2019以及配置anconda教程的方法步骤
Nov 11 Python
tensorflow实现tensor中满足某一条件的数值取出组成新的tensor
Jan 04 Python
基于PyQT实现区分左键双击和单击
May 19 Python
基于python实现操作git过程代码解析
Jul 27 Python
python二维图制作的实例代码
Dec 03 Python
Python批量删除mysql中千万级大量数据的脚本分享
Dec 03 Python
Python 迭代器工具包【推荐】
May 06 #Python
Python中内建函数的简单用法说明
May 05 #Python
Python使用Paramiko模块编写脚本进行远程服务器操作
May 05 #Python
Python环境下搭建属于自己的pip源的教程
May 05 #Python
使用Python判断质数(素数)的简单方法讲解
May 05 #Python
Python编程中归并排序算法的实现步骤详解
May 04 #Python
Python手机号码归属地查询代码
May 04 #Python
You might like
在同一窗体中使用PHP来处理多个提交任务
2008/05/08 PHP
php去除重复字的实现代码
2011/09/16 PHP
PHP中使用curl伪造IP的简单方法
2015/08/07 PHP
两种php去除二维数组的重复项方法
2015/11/04 PHP
golang实现php里的serialize()和unserialize()序列和反序列方法详解
2018/10/30 PHP
Ucren Virtual Desktop V2.0
2006/11/07 Javascript
JS操作Cookies包括(读取添加与删除)
2012/12/26 Javascript
图片无缝滚动代码(向左/向下/向上)
2013/04/10 Javascript
鼠标移入移出事件改变图片的分辨率的两种方法
2013/12/17 Javascript
JS实现随机乱撞彩色圆球特效的方法
2015/05/05 Javascript
JavaScript使用RegExp进行正则匹配的方法
2015/07/11 Javascript
JS简单设置下拉选择框默认值的方法
2016/08/20 Javascript
正则表达式替换html元素属性的方法
2016/11/26 Javascript
Angular2 自定义validators的实现方法
2017/07/05 Javascript
微信小程序将字符串生成二维码图片的操作方法
2018/07/17 Javascript
Vue状态模式实现窗口停靠功能(灵动、自由, 管理后台Admin界面)
2020/03/06 Javascript
浅谈nuxtjs校验登录中间件和混入(mixin)
2020/11/06 Javascript
基于ajax实现上传图片代码示例解析
2020/12/03 Javascript
[01:54]TI珍贵瞬间系列(三):翻盘
2020/08/28 DOTA
浅析Python四种数据类型
2018/09/26 Python
Python 列表去重去除空字符的例子
2019/07/20 Python
Django时区详解
2019/07/24 Python
python 实现多线程下载m3u8格式视频并使用fmmpeg合并
2019/11/15 Python
keras 使用Lambda 快速新建层 添加多个参数操作
2020/06/10 Python
基于TensorFlow的CNN实现Mnist手写数字识别
2020/06/17 Python
简单了解python关键字global nonlocal区别
2020/09/21 Python
文明青少年标兵事迹材料
2014/01/28 职场文书
学习自我鉴定
2014/02/01 职场文书
《油菜花开了》教学反思
2014/02/22 职场文书
初三学生个人自我评定
2014/04/06 职场文书
抗洪抢险事迹材料
2014/05/06 职场文书
经典演讲稿开场白
2014/08/25 职场文书
四年级数学上册教学计划
2015/01/20 职场文书
2016年学校“6﹒26国际禁毒日”宣传活动总结
2016/04/05 职场文书
MySQL中VARCHAR与CHAR格式数据的区别
2021/05/26 MySQL
Django路由层如何获取正确的url
2021/07/15 Python