上一篇介绍了 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()

0x00Python 之禅中提到的最后一条,命名空间(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()

+----------+ |

|

+----------+ +----------+

| B | | D |

+----------+ super(C,self) +----------+

| 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 元类的call总结_Python 类与元类的深度挖掘 I【经验】相关推荐

  1. python面向对象编程的三大特性_Python面向对象总结及类与正则表达式详解

    Python3 面向对象 -------------------------------------------------------------------------------- 一丶面向对象 ...

  2. python类定义的讲解_python面向对象、自定义类等使用实例讲解

    python 面向对象相对别的语言来说缺少两个功能: 1.python不具备重载,重载是指在同一个类中,使得方法有相同的名称,但是有不同的参数列表,但由于python函数具有强大的参数处理功能,因此这 ...

  3. python创建person类用printinfo方法_Python学习期刊Day11类和对象(2),日记,与,下

    今天是2020年2月20日,晴,6~14℃ 一.继承 继承是一种创建新类的方式.新建类将获得原有类的所有属性和方法.原有的类称为父类(超类),新建的类称为子类. Python中继承的方式如下: ''' ...

  4. python定义一个dog类 类属性有名字_python 基础 12 初识类,类方法,类属性

    python 基础 12 初识类,类方法,类属性 # 面向过程 : 想要一个结果 写代码 实现计算结果 # 面向对象开发 : 有哪些角色 角色的属性和技能 两个角色之间是如何交互的 # 复杂的 拥有开 ...

  5. python中的类是什么意思_python中什么是类

    面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的"对象",每个对象都拥有相同的 ...

  6. python是否高送转预测股票_详解:如何深度挖掘高送转板块(附:2018年高送转个股预测)...

    原标题:详解:如何深度挖掘高送转板块(附:2018年高送转个股预测) 不知不觉又到了11月份,又到了高送转板块表现的时候.今天上午小编主要为大家详解高送转板块:什么是高送转.炒作的几个阶段.如何选择有 ...

  7. python oop求三角形面积公式_python学习日记(OOP——类的内置方法)

    __str__和__repr__ 改变对象的字符串显示__str__,__repr__ 我们先定义一个Student类,打印一个实例: classStudent(object):def __init_ ...

  8. python类的成员函数_Python实现动态添加类的属性或成员函数的解决方法

    某些时候我们需要让类动态的添加属性或方法,比如我们在做插件时就可以采用这种方法.用一个配置文件指定需要加载的模块,可以根据业务扩展任意加入需要的模块. 本文就此简述了Python实现动态添加类的属性或 ...

  9. python 类 对象 知乎_python基础知识:类,对象,模块三者的区别

    长话短说,我线代作业还没有写呢. 有点编程语言知识的朋友应该知道,编程语言有"面向对象"和"面向过程"两种. 解释一下:面向对象技术是目前流行的系统设计开发技术 ...

  10. python调用包中的方法_python 中不同包 类 方法 之间的调用详解

    目录结构如下: 在hello.py中导入ORM.py这个文件的时候,采用 import ORMPackage.ORM 或者 import ORM u = User(id = 123, name='co ...

最新文章

  1. 六、OpenStack配置计算结点
  2. LeetCode Algorithm 3. 无重复字符的最长子串
  3. 解决Tomcat下IntelliJ IDEA报错java.lang.NoClassDefFoundError: javax/servlet/ServletContextListener
  4. activemq使用linux内核机制,activemq基础之:(四)CentOS7 Linux搭建activemq
  5. java上传图片特征码到服务器,记一个Base64编码后经网络传输产生的问题
  6. Android入门(四)UI-创建自定义控件
  7. Tomcat8+redis实现session共享
  8. 2022年PC必备的5款软件,功能强大且免费,你用过几个?
  9. Word转PDF怎么转?三种方法快速学会
  10. SQL SERVER恢复数据库时出现Exclusive access could not be obtained because the database is in use
  11. 《林肯传》--[美]戴尔·卡耐基
  12. 4G工业路由器如何助力自动售货机更智能?
  13. matlab 冲激响应不变法,matlab实验七 冲激响应不变法IIR数字滤波器设计
  14. thinkpad申请恢复盘_移动硬盘坏了是什么体验?(含数据恢复和换货全过程)
  15. PyCharm关闭双击shift全局搜索
  16. 今时不同往日:VS2010十大绝技让VS6叹“.NET研究”服
  17. 区块链原理及核心技术
  18. starting to launch local task to process map join maximum memory =1029701632 的原因
  19. nginx代理应用sso跳转处理
  20. 100集华为HCIE安全培训视频教材整理 | 流量型攻击防范技术-DNS

热门文章

  1. Oracle 数据库
  2. 可以操作excel吗_Excel快速填充,这四种方法你会吗?操作逆天告别加班
  3. C++查看各种数据类型所占字节和最大最小值(数据范围)
  4. TensorFlow 中三种启动图用法
  5. OpenCV学习笔记(十三):霍夫变换:HoughLines(),HoughLinesP(),HoughCircles( )
  6. Libcurl安装与HelloWorld
  7. 卷积在计算机中实现+pool作用+数据预处理目的+特征归一化+理解BN+感受野理解与计算+梯度回传+NMS/soft NMS
  8. 吴恩达斯坦福大学机器学习 CS229 课程学习笔记(二)
  9. Android之自定义控件入门
  10. 掌控谈话~让对方说“你说得对