26章 OOP:宏伟蓝图

类是Python面向对象程序设计(Object-Oriented Programming,OOP)的主要工具。概括地讲,类就是一些函数的包,这些函数大量地使用并处理内置对象类型。不过,类的设计是为了创建和管理新的对象,同时它们也支持继承。

1 为何使用类

从以下的两个方面来看OOP非常有用:

  • 继承:比萨制作机器人本质上是一种机器人,它拥有一般机器人的属性。用0OP术语来讲,制作比萨的机器人继承了所有机器人的通用类型的属性。对于这些通用的属性只需将通用的代码实现一次,就能让未来我们所创建的所有种类的机器人都可以重用。
  • 组合:比萨制作机器人其实是一些组件的集合,这些组件作为一个整体共同工作。例如,为了让机器人成功运行,也许会需要滚面团的机械臂和启动烤箱的马达等。用OOP的术语来讲,机器人是一个组合的实例,它包含其他对象,这些对象来完成相应的指令。每个组件都可以写成类,并定义自己的行为以及关系。

从更具体的编程角度来看,类是Python程序的组成单元,就像函数和模块一样:类是封装逻辑和数据的另一种方式。 实际上,类也定义了新的命名空间,这点和模块很像。但是,和其他我们已见过的程序组成单元相比,类有三个重要的独特之处,使其在建立新对象时更为有用:

  • 多重实例:类本质上是产生对象的工厂。每当我们调用一个类的时候,就会产生一个有独立命名空间的新对象。每个由类产生的对象都能读取类的属性,并获得自己的命名空间来储存数据,这些数据是属于每个对象本身的。
  • 通过继承进行定制:类也支持OOP的继承的概念。我们可以在类的外部以编写子类的方式,来重新定义其属性进而扩充这个类。更一般地来说,类可以建立命名空间的层次结构,而这种层次结构可以定义该结构中类创建的对象所使用的名称。这样,我们可以比其他工具更直接地支持多重可定制化的行为。
  • 运算符重载:通过提供特定的协议方法,类可以定义对象来响应在内置类型上的一些运算。例如,通过类创建的对象可以进行切片、拼接和索引等运算。Python 提供了一些可以由类使用的钩子,从而能够拦截并实现任何的内置类型运算。

2 概览OOP

2.1 属性继承搜索

Python中大多数OOP操作,都可以简化为如下这个表达式:

object.attribute

在后面的介绍中我们会一直使用这个表达式读取模块属性,调用对象的方法。然而,当我们对class语句产生的对象使用这种方式时,这个表达式会在Python中启动一次搜索,即搜索对象连接的类树,来寻找attribute首次出现的类。当类参与其中时,上面的Python表达式实际上等同于下列自然语言:

找出attribute首次出现的地方,先搜索object,然后是该对象之上的所有类,由下往上,由右到左

换句话来说,属性访问就只是搜索类树而已。我们称这种搜索为继承,因为树中位置较低的对象继承了树中位置较高的对象所拥有的属性。当从下至上进行搜索时,连接至树中的对象就是树中所有父节点定义的所有属性的并集,直到树的根部。

在Python中可以很直接地理解:我们通过代码建立连接对象树,而每次使用object.attribute表达式时,Python确实会在运行期间去“爬树”,来搜索属性。图26-1是这种树的一个例子:

在这里插入图片描述
在图26-1中展示了一棵包含了五个对象的类树,其中的每个对象都附带着待搜索的属性。更确切地讲,该树把三个类的对象(椭圆C1、C2以及C3)和两个实例对象(矩形I1和I2)连接至继承搜索树。就搜索树来看,实例从它的类继承属性,而类则是从搜索树中所有比它更高的类中继承属性。

在图26-1中,我们可以按照椭圆形在树中的相对位置再进一步分类。我们通常把树中位置较高的类称为父类(superclass,也称为基类,比如C2和C3)。树中位置较低的类则称为子类(subclass,也称为派生类,比如C1)。这些术语表明了树中的相对位置和角色。父类提供了所有子类共享的行为,但是因为搜索过程是自底向上的,所以子类可能会在树中较低位置重新定义父类的名称,从而覆盖父类定义的行为。

假设我们创建了如图26-1所示的树,然后编写了:

I2.w

这个代码会立即启用继承。因为这是一个object.attribute表达式,所以它会触发图26-1中对树的搜索,也就是说Python会查看I2和更高的对象来搜索属性w。更确切地讲,就是按照下面这个顺序搜索连接的对象:

I2, C1, C2, C3

找到首个w之后就会停止搜索(但如果找不到w,就引发一个错误)。此例中,直到搜索C3时才会找到w,因为w只出现在了该对象内。也就是说,通过自动搜索,I2.w会解析为C3.W。用OOP术语来说,I2从C3“继承”了 属性W。

最终,这两个实例从对应的类中继承了四个属性: W. x、y和z。不同属性的引用则会循着树中不同的路径进行。如下所示:

  • I1.x和I2.x两者都会在C1中找到x并停止搜索,因为C1比C2位置更低。
  • I1.y和I2.y两者都会在C1中找到y,因为这里是y唯一出现的地方。
  • I1.z和I2.z两者都会在C2中找到z,因为C2比C3更靠左侧。
  • l2.name会找到I2中的name,完全不需要“ 爬树”

上面列出的第一项也许是最需要注意的:因为C1在树中较低的地方重新定义了属性x,相当于实际上取代了其上C2中的版本。这样的重新定义就是OOP的一个核心。通过重新定义和取代属性,C1有效地定制了它从父类中所继承的属性。

2.2 类和实例

在这里插入图片描述
在这里插入图片描述

2.3 方法调用

2.1节中展示了属性的引用I2.w是怎样通过Python中的继承搜索转换为C3.w的。接下来介绍调用方法(也就是附属于类的函数属性)。

如果这个I2.w引用是一个函数调用,其实际的含义是“调用C3.W函数来处理I2”。也就是说,Python将会自动将I2.W()调用映射为C3.W(I2)调用,同时传入该实例作为继承的函数的第一位参数。

事实上,每当我们以这种方式调用附属于类的函数时,总会隐含着这个类的实例。这个隐含的主体或上下文就是将其称之为面向对象模型的一部分原因:当操作执行时,总是有个主体对象。在更实际的例子中,我们可能会启用名为giveRaise的方法(附属于Employee类的属性)。除非和应该加薪的员工结合在一起使用,不然这种调用是没有意义的。???

Python把隐含的实例传入作为方法中的特殊第一位参数,习惯上将其称为self。方法通过这个参数来处理调用的主体。方法还能通过实例(例如bob.giveRaise())或类(例如Employee.giveRaise(bob)进行调用。这些调用也展示了0OP中的两个思想:Python 在运行bob.giveRaise()方法调用时做了两件事:

  • 在bob中通过继承搜索寻找giveRaise。
  • 将bob传入找到的giveRaise函数,并赋值给self参数。

在这里插入图片描述

2.4 编写类树

我们用class语句和类调用来构造树和其中的对象,简而言之:

  • 每个class语句会生成一个新的类对象
  • 每次类调用时,就会生成一个新的实例对象
  • 实例自动链接到创建它们的类
  • 类链接到其父类的方式是,将父类列在calss头部的括号内;括号中从左到右的顺序会决定树中的次序。

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.5 运算符重载

待补充 30

2.6 OOP是关于代码重用
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值