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获取beautifulphoto随机某图片代码实例
Dec 18 Python
Django原生sql也能使用Paginator分页的示例代码
Nov 15 Python
使用python实现BLAST
Feb 12 Python
python如何在列表、字典中筛选数据
Mar 19 Python
python3实现基于用户的协同过滤
May 31 Python
简单谈谈python基本数据类型
Sep 26 Python
numpy linalg模块的具体使用方法
May 26 Python
简单了解python中对象的取反运算符
Jul 01 Python
python3读取图片并灰度化图片的四种方法(OpenCV、PIL.Image、TensorFlow方法)总结
Jul 04 Python
matplotlib quiver箭图绘制案例
Apr 17 Python
Python使用sqlite3模块内置数据库
May 07 Python
django form和field具体方法和属性说明
Jul 09 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函数,php爱好者站推荐
2007/03/19 PHP
php 遍历显示文件夹下所有目录、所有文件的函数,没有分页的代码
2008/11/14 PHP
实用的PHP带公钥加密类分享(每次加密结果都不一样哦)
2014/08/20 PHP
typecho插件编写教程(二):写一个新插件
2015/05/28 PHP
PHP实现HTTP断点续传的方法
2015/06/17 PHP
详解PHP swoole process的使用方法
2017/08/26 PHP
网站导致浏览器崩溃的原因总结(多款浏览器) 推荐
2010/04/15 Javascript
关于锚点跳转及jQuery下相关操作与插件
2012/10/01 Javascript
JavaScript函数参数使用带参数名的方式赋值传入的方法
2015/03/19 Javascript
JavaScript对象数组的排序处理方法
2015/10/21 Javascript
利用jquery禁止外层滚动条的滚动
2017/01/05 Javascript
canvas红包照片实例分享
2017/02/28 Javascript
javascript闭包功能与用法实例分析
2017/04/06 Javascript
AngularJS 购物车全选/取消全选功能的实现方法
2017/08/14 Javascript
axios中cookie跨域及相关配置示例详解
2017/12/20 Javascript
node.js博客项目开发手记
2018/03/16 Javascript
[51:20]完美世界DOTA2联赛PWL S2 Magma vs PXG 第一场 11.28
2020/12/01 DOTA
Python中让MySQL查询结果返回字典类型的方法
2014/08/22 Python
酷! 程序员用Python带你玩转冲顶大会
2018/01/17 Python
Python读取txt内容写入xls格式excel中的方法
2018/10/11 Python
使用APScheduler3.0.1 实现定时任务的方法
2019/07/22 Python
对Django中内置的User模型实例详解
2019/08/16 Python
Python实现微信机器人的方法
2019/09/06 Python
python对象转字典的两种实现方式示例
2019/11/07 Python
django多种支付、并发订单处理实例代码
2019/12/13 Python
django实现将后台model对象转换成json对象并传递给前端jquery
2020/03/16 Python
将python字符串转化成长表达式的函数eval实例
2020/05/11 Python
python用tkinter实现一个gui的翻译工具
2020/10/26 Python
自荐信封面
2013/12/04 职场文书
一名女生的自荐信
2013/12/08 职场文书
汽车技术服务英文求职信范文
2014/01/02 职场文书
事业单位公务员的职业生涯规划
2014/01/15 职场文书
房地产开盘策划方案
2014/02/10 职场文书
海洋科学专业求职信
2014/08/10 职场文书
升学宴家长答谢词
2015/09/29 职场文书
python基于opencv批量生成验证码的示例
2021/04/28 Python