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的Django框架中inclusion_tag的使用
Jul 21 Python
Python装饰器用法示例小结
Feb 11 Python
python实现感知器算法(批处理)
Jan 18 Python
python 中如何获取列表的索引
Jul 02 Python
python实现屏保程序(适用于背单词)
Jul 30 Python
Python collections模块使用方法详解
Aug 28 Python
Python高级特性之闭包与装饰器实例详解
Nov 19 Python
Python识别html主要文本框过程解析
Feb 18 Python
python异步Web框架sanic的实现
Apr 27 Python
Python3批量创建Crowd用户并分配组
May 20 Python
python 常用的异步框架汇总整理
Jun 18 Python
详解pytorch创建tensor函数
Mar 22 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
使用ThinkPHP自带的Http类下载远程图片到本地的实现代码
2011/08/02 PHP
PHP编码转换
2012/11/05 PHP
PHP 过滤页面中的BOM(实现代码)
2013/06/29 PHP
PHP使用内置函数file_put_contents写入文件及追加内容的方法
2015/12/07 PHP
PHPMailer使用QQ邮箱实现邮件发送功能
2017/08/18 PHP
php实现生成带二维码图片并强制下载功能
2018/02/24 PHP
PHP中in_array的隐式转换的解决方法
2018/03/06 PHP
PHP删除字符串中非字母数字字符方法总结
2019/01/20 PHP
one.php 多项目、函数库、类库 统一为一个版本的方法
2020/08/24 PHP
jquery ajax学习笔记2 使用XMLHttpRequest对象的responseXML
2011/10/16 Javascript
JS增加行复制行删除行的实现代码
2013/11/09 Javascript
jquery对单选框,多选框,文本框等常见操作小结
2014/01/08 Javascript
JS实现数字格式千分位相互转换方法
2016/08/01 Javascript
jQuery web 组件 后台日历价格、库存设置的代码
2016/10/14 Javascript
vue cli 3.x 项目部署到 github pages的方法
2019/04/17 Javascript
详解vue中使用axios对同一个接口连续请求导致返回数据混乱的问题
2019/11/06 Javascript
Nuxt的动态路由和参数校验操作
2020/11/09 Javascript
在Python中使用itertools模块中的组合函数的教程
2015/04/13 Python
Python 加密与解密小结
2018/12/06 Python
python+opencv实现摄像头调用的方法
2019/06/22 Python
python通过链接抓取网站详解
2019/11/20 Python
python科学计算之scipy——optimize用法
2019/11/25 Python
使用 tf.nn.dynamic_rnn 展开时间维度方式
2020/01/21 Python
使用python实现下载我们想听的歌曲,速度超快
2020/07/09 Python
Jupyter安装链接aconda实现过程图解
2020/11/02 Python
Bobbi Brown芭比波朗美国官网:化妆师专业彩妆保养品品牌
2016/08/18 全球购物
迪奥美国官网:Dior美国
2019/12/07 全球购物
SQL SERVER面试资料
2013/03/30 面试题
Java面试题:Java类的Main方法如果是Private将会怎么样
2016/08/18 面试题
我的中国梦口号
2014/06/16 职场文书
贯彻落实“八项规定”思想汇报
2014/09/13 职场文书
个人违纪检讨书
2014/09/15 职场文书
公司副总经理岗位职责
2014/10/01 职场文书
酒店服务员岗位职责
2015/02/09 职场文书
幼儿园个人总结
2015/02/28 职场文书
Python异常类型以及处理方法汇总
2021/06/05 Python