面向对象(OOP)
面向对象编程- Object Oriented Programming
简写 OOP
1. 面向对象基本概念
- 我们之前学习的编程方式就是 面向过程 的
- 面向过程 和 面向对象,是两种不同的 编程方式
- 对比 面向过程 的特点,可以更好的了解什么是 面向对象
1.1 过程和函数(科普)
- 过程 是早期的一个编程概念
- 过程 类似于函数,只能执行,但是没有返回值
- 函数 不仅能执行,还可以返回结果
1.2 面向过程和面向对象的基本概念
面向过程-怎么做?
- 把完成某一个需求的
所有步骤
从头到尾
逐步实现
- 把完成某一个需求的
根据开发需求,将某些 功能独立 的代码 封装 成一个又一个 函数
最后完成的代码,就是顺序地调用 不同的函数
特点
注重 步骤与过程,不注重职责分工
如果需求复杂,代码会变得很复杂
开发复杂项目,没有固定的套路,开发难度很大
面向对象-谁来做?
在完成某一个需求前,首先确定 职责-要做的事情(方法)
根据 职责 确定不同的 对象,在 对象 内部封装不同的 方法 (多个)
最后完成的代码,就是顺序地让 不同的对象 调用 不同的方法
特点
注重 对象和职责,不同的对象承担不同的职责
更加适合应对复杂的需求变化,是专门应对复杂项目开发,提供的固定套路
需要在面向过程基础上,再学习一些面向对象的语法
相比较函数,面向对象是更大的封装 ,根据 职责 在 一个对象中封装多个方法
2. 类和对象
2.1 类和对象的概念
类 和 对象 是 面向对象编程的两个核心概念
类
类 是对一群具有 相同特征 或者 行为 的事物的一个统称,是抽象的,不能直接使用
- 特征 被称为 属性
- 行为 被称为 方法
类 就相当于制造飞机时的 图纸 ,是一个 模板,是 负责创建对象的
对象
对象 是 由类创建出来的一个具体存在,可以直接使用
由 哪一个类 创建出来的 对象,就拥有在 哪一个类 中定义的:
- 属性
- 方法
对象 就相当于用 图纸 制造 的飞机
在程序开发中,应该先有类,再有对象
2.2 类和对象的关系
类是模板,对象 是根据 类 这个模板创建出来的,应该 先有类,再有对象
类 只有一个,而 对象 可以有很多个
- 不同的对象 之间 属性 可能会各不相同
类 中定义了什么 属性和方法,对象 中就有什么属性和方法,不可能多,也不可能少
2.3 类的设计
在使用面向对象开发前,应该首先分析需求,确定一下,程序中需要包含哪些类
在程序开发中,要设计一个类,通常需要满足以下三个要素:
- 类名 这类事物的名字,满足驼峰命名法
- 属性 这类事物具有什么样的特征
- 方法 这类事物具有什么样的行为
驼峰命名法
例:CapWords
- 每一个单词的首字母大写
- 单词与单词之间没有下划线
2.4 类名的确定
名词提炼法 分析 整个业务流程,出现的 名词,通常就是找到的类
2.5 属性和方法的确定
对 对象的特征描述,通常可以定义成 属性
对象具有的行为(动词),通常可以定义成 方法
提示:需求中没有涉及的属性或者方法在设计类时,不需要考虑
3.面向对象基础语法
3.1 dir内置函数(拓展)
- 在
Python
中 对象几乎是无所不在的,我们之前学习的 变量,函数 都是对象
在 Python
中可以使用以下两个方法验证:
- 在 标识符/数据 后输入一个
.
,然后按下TAB
键,iPython
会提示该对象能够调用的 方法列表 - 使用内置函数
dir
传入 标识符/数据,可以查看对象内的 所有属性及方法
提示 __方法名__
格式的方法是 Python
提供的 内置方法/属性,下面是一些常用的内置方法/属性:
序号 | 方法名 | 类型 | 作用 |
---|---|---|---|
01 | _new_ | 方法 | 创建对象时,会被自动调用 |
02 | _init_ | 方法 | 对象被初始化时,会被自动调用 |
03 | _del_ | 方法 | 对象被从内存中销毁前,会被自动调用 |
04 | _str_ | 方法 | 返回对象的描述信息,print 函数输出使用 |
提示 利用好 dir()
函数,在学习时很多内容不需要死记硬背了
3.2 定义简单的类(包含方法)
面向对象 是 更大 的 封装,在 一个类中 封装 很多个方法,这样 通过这个类创建出来的对象,就可以直接调用这些方法了
定义只包含方法的类
在
Python
中要定义一个包含方法的类,语法格式如下:class 类名: def 方法1(self, 参数): pass def 方法2(self, 参数): pass
方法 的定义格式和之前学习过的 函数 几乎一样
区别在于第一个参数必须是
self
注意: 类名的命名规则要符合驼峰命名法
创建对象
当一个类定义完成之后,要使用这个类来创建对象,语法格式如下:
对象变量 = 类名()
设计一个面向对象程序
需求
- 小猫爱吃鱼,小猫要喝水
分析
定义一个猫类
Cat
定义两个方法
eat
和drink
按照需求-不需要定义属性
class Cat: '''这是一个猫类''' def eat(self): print('小猫爱吃鱼') def drink(self): print('小猫在喝水') tom = Cat() tom.drink() tom.eat()
引用概念的强调
在面向对象开发中,引用 的概念是同样适用的
- 在
Python
中使用类 创建对象之后,tom
变量中仍然记录的是 对象在内存中的内存地址
也就是
tom
变量 引用 了 新建的猫对象使用
print
输出 对象变量,默认情况下,是能够输出这个变量 引用的对象 是 由哪一个类创建的对象,以及 在内存中的地址(十六进制表示)提示:在计算机中,通常使用 十六进制 表示 内存地址
- 十进制 和 十六进制 都是用来表达数字的,只是表示的方式不一样
- 十进制 和 十六进制 的数字之间可以来回转换
%d
可以以 10进制 输出数字%s
可以以 16进制 输出数字
方法中的 self 参数
案例改造-给对象增加属性
在
Python
中,要 给对象设置属性,非常的容易,但是不推荐使用- 因为对象属性的封装应该封装在类的内部
只需要在 类的外部的代码 中直接通过,设置一个属性即可
注意:这种方式虽然简单,但是不推荐使用
tom.name = 'Tom' ... lazy_cat.name = '大懒猫'
使用 self 在方法内部输出每一只猫的名字
由哪一个对象调用的方法,方法内的
self
就是哪一个对象引用的- 在类封装的方法内部,
self
就表示 当前调用方法的对象自己
- 在类封装的方法内部,
调用方法时,程序员不需要传递
self
参数在方法内部 也可以通过
self.
,访问对象的属性
4. 初始化方法
4.1 之前代码存在的问题-在类的外部给对象增加属性
将案例代码进行调整,先调用方法再设置属性,观察一下执行结果
tom = Cat() tom.drink() tom.eat() tom.name = 'Tom' print(tom)
程序执行报错如下:
AttributeError: 'Cat' object has no attribute 'name' 属性错误: ‘Cat’对象没有‘name’属性
提示
在日常开发中,不推荐在 类的外部 给对象增加属性
- 如果 在日常运行时,没有找到属性,程序会报错
对象应该包含有哪些属性,应该 封装在类的内部
4.2 初始化方法
当使用
类名()
创建对象时,会 自动 执行以下操作:- 为对象在内存中 分配空间-创建对象
- 为对象的属性 设置初始值-初始化方法(init)
这个 初始化方法 就是
__init__
方法,__init__
是对象的 内置方法__init__
方法是专门用来定义一个类 具有哪些属性的方法在
Cat
中增加__init__
方法,验证该方法在创建对象时会被自动调用class Cat: '''这是一个猫类''' def __init__(self): print('初始化方法')
4.3 在初始化方法内部定义属性
在
__init__
方法内部使用self.属性名 = 属性的初始值
就可以 定义属性定义属性之后,再使用
Cat
类创建对象,都会拥有该属性class Cat: def __init__(self): print('这是一个初始化方法') # 定义用 Cat 类创建的猫对象都有一个 name 的属性 self.name = 'Tom' def eat(self): print('{} 爱吃鱼'.format(self.name)) # 使用类名()创建对象的时候,会自动使用初始化方法 __init__ tom = Cat() tom.eat()
4.4 改造初始化方法-初始化的同时设置初始值
在开发中,如果希望在 创建对象的同时,就设置对象的属性,可以对
__init__
方法进行 改造把希望设置的属性值,定义成
__init__
方法的参数在方法内部使用
self.属性 = 行参
接收外部传递的参数在创建对象时,使用
类名(属性1,属性2...)
调用class Cat: def __init__(self, name): print('初始化方法 {}'.format(name)) self.name = name ... tom = Cat('Tom') ... lazy_cat = Cat('大懒猫') ...
5.内置方法和属性
序号 | 方法名 | 类型 | 作用 |
---|---|---|---|
01 | _del_ | 方法 | 对象被从内存中销毁前,会被 自动 调用 |
02 | _str_ | 方法 | 返回 对象的描述信息,print 函数输出使用 |
5.1 _del_ 方法(知道)
在
Python
中- 当使用
类名()
创建对象时,为对象 分配完空间 后,自动 调用__init__
方法 - 当一个 对象被从内存中销毁 前,会 自动 调用
__del__
方法
- 当使用
应用场景
__init__
改造初始化方法,可以让创建对象更加灵活__del__
如果希望在对象被销毁前,再做一些事情,可以考虑一下__del__
方法
生命周期
- 一个对象从调用
类名()
创建,生命周期开始 - 一个对象的
__del__
方法一旦被调用,生命周期结束 - 在对象的生命周期内,可以访问对象属性,或者让对象调用方法
- 一个对象从调用
5.2 _str_ 方法
在
Python
中,使用print
输出 对象变量,默认情况下,会输出这个变量 引用的对象 是 由哪一个类创建的对象,以及 在内存中的地址(十六进制表示)如果在开发中,希望使用
print
输出 对象变量 时,能够打印 自定义的内容,就可以利用__str__
这个内置方法了注意:
__str__
方法必须返回一个字符串class Cat: def __init__(self, new_name): self.name = new_name print('{} 来了'.format(self.name)) def __del__(self): print('{} 去了'.format(self.name)) def __str__(self): return '我是小猫 {}'.format(self.name) tom = Cat('Tom') print(tom)
6. 类的结构
6.1 术语-实例
- 在使用面向对象开发,第一步是设计类
- 使用
类名()
创建对象,创建对象 的动作有两步:- 在内存中为对象 分配空间
- 调用初始化方法
__init__
为 对象初始化
- 对象创建后,内存 中就有了一个对象的 实实在在 的存在-实例
因此,通常也会把:
创建出来的 对象 叫做 类 的 实例
创建对象的 动作 叫做 实例化
对象的属性 叫做 实例属性
对象调用的方法 叫做 实例方法
在程序执行时:
- 对象各自拥有自己的 实例属性
- 调用对象方法,可以通过
self
- 访问自己的属性
- 调用自己的方法
结论
- 每一个对象 都有自己 独立的内存空间,保存各自不同的属性
- 多个对象的方法,在内存中只有一份,在调用方法时,需要把对象的引用 传递到方法内部
6.2 类是一个特殊的对象
Python
中 一切皆对象:
class AAA:
定义的类属于 *类对象***obj1 = AAA()
属于 实例对象
在程序运行时,类 同样 会被加载到内存
在
Python
中,类 是一个特殊的对象-类对象在程序运行时,类对象 在内存中 只有一份使用一个类,可以创建出 很多个对象实例
除了封装 实例 的 属性 和 方法 外,类对象 还可以拥有自己的 属性 和 方法
- 类属性
- 类方法
通过 类名 的方式可以 访问类的属性 或者 调用类的方法
6.3 类属性和实例属性
概念和使用
- 类属性 就是给 类对象 中定义的 属性
- 通常用来记录 与这个类相关 的特征
- 类属性 不会用于记录 具体对象的特征
示例需求
定义一个 工具类
每件工具都有自己的
name
需求-知道使用这个类,创建了多少个工具对象
class Tool(object): # 使用赋值语句,定义类属性,记录创建工具对象的总数 count = 0 def __init__(self): self.name = name # 针对类属性做一个计数+1 Tool.count += 1 # 创建工具对象 tool1 = Tool('斧头') tool2 = Tool('榔头') tool3 = Tool('铁锹') # 知道使用 Tool 类到底创建了多少个对象? print('现在创建了{}个工具'.format(Tool.count))
属性的获取机制(科普)
在
Python
中 属性的获取 存在一个 向上查找机制因此,要访问类属性有两种方式:
- 类名.类属性
- 对象.类属性(不推荐)
注意
- 如果使用
对象.类属性 = 值
赋值语句,只会 给对象添加一个属性,而不会影响到 类属性的值
6.4 类方法和静态方法
类方法
类属性 就是针对 类对象 定义的属性
- 使用 赋值语句 在
class
关键字下方可以定义 类属性 - 类属性 用于记录 与这个类相关 的特征
- 使用 赋值语句 在
类方法 就是针对 类对象 定义的方法
- 在 类方法 内部可以直接访问 类属性 或者调用其他的 类方法
语法如下
@classmethod def 类方法名(cls): pass
类方法需要用 修饰器
@classmethod
来标识,告诉解释器这是一个类方法类方法的 第一个参数 应该是
cls
- 由 哪一个类 调用的方法,方法内的
cls
就是 哪一个类的引用 - 这个参数和 实例方法 的第一个参数是
self
类似 - 提示 使用其他名称也可以,不过习惯使用
cls
- 由 哪一个类 调用的方法,方法内的
通过 类名,调用 类方法,调用方法时,不需要传递
cls
参数在方法内部
- 可以通过
cls
,访问类的属性 - 也可以通过
cls
,调用其他的类方法
- 可以通过
示例需求
定义一个 工具类
每件工具都有自己的
name
需求-在 类 封装一个
show_tool_count
的类方法,输出使用当前这个类,创建的对象个数@classmethod def show_tool_count(cls): '''显示工具对象的总数''' print('工具对象的总数:{}'.format(cls.count))
在类方法内部,可以直接使用
cls
访问 *类属性** 或者 调用类方法*
静态方法
在开发时,如果需要在 类 中封装一个方法,这个方法:
- 既 不需要 访问 实例属性 或者调用 实例方法
- 也 不需要 访问 类属性 或者调用 类方法
这个时候,可以把这个方法封装成一个 静态方法
语法如下
@staticmethod def 静态方法(): pass
静态方法 需要用 修饰器
@staticmethod
来标识,告诉解释器这是一个静态方法通过 类名,调用 静态方法
class Dog(object): # 狗对象计数 dog_count = 0 @staticmethod def run(): # 不需要访问实例属性也不需要访问类属性的方法 print('狗在跑...') def __init__(self, name): self.name = name
方法综合案例
需求
设计一个
Game
类属性:
- 定义一个 类属性
top_score
记录着游戏的历史最高分 - 定义一个 实例属性
player_name
记录 当前游戏的玩家姓名
- 定义一个 类属性
方法:
- 静态方法
show_help
显示游戏帮助信息 - 类方法
show_top_score
显示历史最高分 - 实例方法
start_game
开始当前玩家的游戏
- 静态方法
主程序步骤
- 查看帮助信息
- 查看历史最高分
- 创建游戏对象,开始游戏
class Game(object): # 历史最高分 top_score = 0 def __init__(self, player_name): self.player_name = player_name @staticmethod def show_help(): print('帮助信息:让僵尸进入大门') @classmethod def show_top_score(cls): print('历史记录 {}'.format(cls.top_score)) def start_game(self): print('{} 开始游戏'.format(self.player_name)) # 1. 查看游戏的帮助信息 Game.show_help() # 2. 查看历史最高分 Game.show_top_score() # 3. 创建游戏对象 game = Game('小明') # 4. 开始游戏 game.start_game()
7. 继承
7.1 什么是面向对象的继承
继承(英语:inheritance)是面向对象软件技术当中的一个概念。如果一个类别A“继承自”另一个类别B,就把这个A称为“B的子类别”,而把B称为“A的父类别”也可以称“B是A的超类”。继承可以使得子类别具有父类别的各种属性和方法,而不需要再次编写相同的代码。在令子类别继承父类别的同时,可以重新定义某些属性,并重写某些方法,即覆盖父类别的原有属性和方法,使其获得与父类别不同的功能。另外,为子类别追加新的属性和方法也是常见的做法。 一般静态的面向对象编程语言,继承属于静态的,意即在子类别的行为在编译期就已经决定,无法在执行期扩充。
字面意思就是:子承父业,合法继承家产,就是如果你是独生子,而且你也很孝顺,不出意外,你会继承你父母所有家产,他们的所有财产都会由你使用(败家子儿除外)。
那么用一个例子来看一下继承:
class Person:
def __init__(self,name,sex,age):
self.name = name
self.age = age
self.sex = sex
class Cat:
def __init__(self,name,sex,age):
self.name = name
self.age = age
self.sex = sex
class Dog:
def __init__(self,name,sex,age):
self.name = name
self.age = age
self.sex = sex
# 继承的用法:
class Aniaml(object):
def __init__(self,name,sex,age):
self.name = name
self.age = age
self.sex = sex
class Person(Aniaml):
pass
class Cat(Aniaml):
pass
class Dog(Aniaml):
pass
继承的有点也是显而易见的:
- 增加了类的耦合性(耦合性不宜多,宜精)。
- 减少了重复代码。
- 使得代码更加规范化,合理化。
7.2 继承的分类
就像上面的例子:
Aminal 叫做父类,基类,超类。
Person Cat Dog: 子类,派生类。
继承:可以分单继承,多继承。
这里需要补充一下python中类的种类(继承需要):
在python2x版本中存在两种类.:
⼀个叫经典类. 在python2.2之前. ⼀直使⽤的是经典类. 经典类在基类的根如果什么都不写.
⼀个叫新式类. 在python2.2之后出现了新式类. 新式类的特点是基类的根是object类。
python3x版本中只有一种类:
python3中使⽤的都是新式类. 如果基类谁都不继承. 那这个类会默认继承 object
7.3 单继承
类名,对象执行父类方法
class Aniaml(object):
type_name = '动物类'
def __init__(self,name,sex,age):
self.name = name
self.age = age
self.sex = sex
def eat(self):
print(self)
print('吃东西')
class Person(Aniaml):
pass
class Cat(Aniaml):
pass
class Dog(Aniaml):
pass
# 类名:
print(Person.type_name) # 可以调用父类的属性,方法。
Person.eat(111)
print(Person.type_name)
# 对象:
# 实例化对象
p1 = Person('春哥','男',18)
print(p1.__dict__)
# 对象执行类的父类的属性,方法。
print(p1.type_name)
p1.type_name = '666'
print(p1)
p1.eat()
执行顺序
class Aniaml(object):
type_name = '动物类'
def __init__(self,name,sex,age):
self.name = name
self.age = age
self.sex = sex
def eat(self):
print(self)
print('吃东西')
class Person(Aniaml):
def eat(self):
print('%s 吃饭'%self.name)
class Cat(Aniaml):
pass
class Dog(Aniaml):
pass
p1 = Person('barry','男',18)
# 实例化对象时必须执行__init__方法,类中没有,从父类找,父类没有,从object类中找。
p1.eat()
# 先要执行自己类中的eat方法,自己类没有才能执行父类中的方法。
同时执行类以及父类方法
方法一:
如果想执行父类的func方法,这个方法并且子类中夜用,那么就在子类的方法中写上:
父类.func(对象,其他参数)
举例说明:
class Aniaml(object):
type_name = '动物类'
def __init__(self,name,sex,age):
self.name = name
self.age = age
self.sex = sex
def eat(self):
print('吃东西')
class Person(Aniaml):
def __init__(self,name,sex,age,mind):
'''
self = p1
name = '春哥'
sex = 'laddboy'
age = 18
mind = '有思想'
'''
# Aniaml.__init__(self,name,sex,age) # 方法一
self.mind = mind
def eat(self):
super().eat()
print('%s 吃饭'%self.name)
class Cat(Aniaml):
pass
class Dog(Aniaml):
pass
# 方法一: Aniaml.__init__(self,name,sex,age)
# p1 = Person('春哥','laddboy',18,'有思想')
# print(p1.__dict__)
# 对于方法一如果不理解:
# def func(self):
# print(self)
# self = 3
# func(self)
方法二:
利用super,super().func(参数)
class Aniaml(object):
type_name = '动物类'
def __init__(self,name,sex,age):
self.name = name
self.age = age
self.sex = sex
def eat(self):
print('吃东西')
class Person(Aniaml):
def __init__(self,name,sex,age,mind):
'''
self = p1
name = '春哥'
sex = 'laddboy'
age = 18
mind = '有思想'
'''
# super(Person,self).__init__(name,sex,age) # 方法二
super().__init__(name,sex,age) # 方法二
self.mind = mind
def eat(self):
super().eat()
print('%s 吃饭'%self.name)
class Cat(Aniaml):
pass
class Dog(Aniaml):
pass
# p1 = Person('春哥','laddboy',18,'有思想')
# print(p1.__dict__)
单继承练习题:
# 1
class Base:
def __init__(self, num):
self.num = num
def func1(self):
print(self.num)
class Foo(Base):
pass
obj = Foo(123)
obj.func1() # 123 运⾏的是Base中的func1
# 2
class Base:
def __init__(self, num):
self.num = num
def func1(self):
print(self.num)
class Foo(Base):
def func1(self):
print("Foo. func1", self.num)
obj = Foo(123)
obj.func1() # Foo. func1 123 运⾏的是Foo中的func1
# 3
class Base:
def __init__(self, num):
self.num = num
def func1(self):
print(self.num)
class Foo(Base):
def func1(self):
print("Foo. func1", self.num)
obj = Foo(123)
obj.func1() # Foo. func1 123 运⾏的是Foo中的func1
# 4
class Base:
def __init__(self, num):
self.num = num
def func1(self):
print(self.num)
self.func2()
def func2(self):
print("Base.func2")
class Foo(Base):
def func2(self):
print("Foo.func2")
obj = Foo(123)
obj.func1() # 123 Foo.func2 func1是Base中的 func2是⼦类中的
# 再来
class Base:
def __init__(self, num):
self.num = num
def func1(self):
print(self.num)
self.func2()
def func2(self):
print(111, self.num)
class Foo(Base):
def func2(self):
print(222, self.num)
lst = [Base(1), Base(2), Foo(3)]
for obj in lst:
obj.func2() # 111 1 | 111 2 | 222 3
# 再来
class Base:
def __init__(self, num):
self.num = num
def func1(self):
print(self.num)
self.func2()
def func2(self):
print(111, self.num)
class Foo(Base):
def func2(self):
print(222, self.num)
lst = [Base(1), Base(2), Foo(3)]
for obj in lst:
obj.func1() # 拿笔来吧. 好好算
7.4 多继承
class ShenXian: # 神仙
def fei(self):
print("神仙都会⻜")
class Monkey: # 猴
def chitao(self):
print("猴⼦喜欢吃桃⼦")
class SunWukong(ShenXian, Monkey): # 孙悟空是神仙, 同时也是⼀只猴
pass
sxz = SunWukong() # 孙悟空
sxz.chitao() # 会吃桃⼦
sxz.fei() # 会⻜
此时, 孙悟空是⼀只猴⼦, 同时也是⼀个神仙. 那孙悟空继承了这两个类. 孙悟空⾃然就可以执⾏这两个类中的⽅法. 多继承⽤起来简单. 也很好理解. 但是多继承中, 存在着这样⼀个问题. 当两个⽗类中出现了重名⽅法的时候. 这时该怎么办呢? 这时就涉及到如何查找⽗类⽅法的这么⼀个问题.即MRO(method resolution order) 问题. 在python中这是⼀个很复杂的问题. 因为在不同的python版本中使⽤的是不同的算法来完成MRO的.
这里需要补充一下python中类的种类(继承需要):
在python2x版本中存在两种类.:
⼀个叫经典类. 在python2.2之前. ⼀直使⽤的是经典类. 经典类在基类的根如果什么都不写.
⼀个叫新式类. 在python2.2之后出现了新式类. 新式类的特点是基类的根是object类。
python3x版本中只有一种类:
python3中使⽤的都是新式类. 如果基类谁都不继承. 那这个类会默认继承 object
经典类的多继承
虽然在python3中已经不存在经典类了. 但是经典类的MRO最好还是学⼀学. 这是⼀种树形结构遍历的⼀个最直接的案例. 在python的继承体系中. 我们可以把类与类继承关系化成⼀个树形结构的图. 来, 上代码:
class A:
pass
class B(A):
pass
class C(A):
pass
class D(B, C):
pass
class E:
pass
class F(D, E):
pass
class G(F, D):
pass
class H:
pass
class Foo(H, G):
pass
对付这种 mro 画图就可以:
继承关系图已经有了. 那如何进⾏查找呢? 记住⼀个原则. 在经典类中采⽤的是深度优先,遍历⽅案. 什么是深度优先. 就是⼀条路走到头. 然后再回来. 继续找下⼀个:
图中每个圈都是准备要送鸡蛋的住址. 箭头和⿊线表⽰线路. 那送鸡蛋的顺序告诉你入⼝在最下⾯R. 并且必须从左往右送. 那怎么送呢?
如图. 肯定是按照123456这样的顺序来送. 那这样的顺序就叫深度优先遍历. ⽽如果是142356呢? 这种被称为⼴度优先遍历. 好了. 深度优先就说这么多. 那么上⾯那个图怎么找的呢? MRO是什么呢? 很简单. 记住. 从头开始. 从左往右. ⼀条路跑到头, 然后回头. 继续⼀条路跑到头. 就是经典类的MRO算法.
类的MRO: Foo-> H -> G -> F -> E -> D -> B -> A -> C. 你猜对了么?
新式类的多继承
mro序列
MRO是一个有序列表L,在类被创建时就计算出来。
通用计算公式为:
mro(Child(Base1,Base2)) = [ Child ] + merge( mro(Base1), mro(Base2), [ Base1, Base2] )
(其中Child继承自Base1, Base2)
如果继承至一个基类:class B(A)
这时B的mro序列为:
mro( B ) = mro( B(A) )
= [B] + merge( mro(A) + [A] )
= [B] + merge( [A] + [A] )
= [B,A]
如果继承至多个基类:class B(A1, A2, A3 …)
这时B的mro序列:
mro(B) = mro( B(A1, A2, A3 …) )
= [B] + merge( mro(A1), mro(A2), mro(A3) ..., [A1, A2, A3] )
= ...
计算结果为列表,列表中至少有一个元素即类自己,如上述示例[A1,A2,A3]。merge操作是C3算法的核心。
表头和表尾
表头
- 列表的第一个元素
表尾
- 列表中表头以外的元素集合(可以为空)
示例
- 列表:[A, B, C]
- 表头是A,表尾是B和C
- 列表:[A, B, C]
列表之间的 + 操作
- +操作:
- [A] + [B] = [A, B]
merge操作示例:
如计算merge( [E,O], [C,E,F,O], [C] )
有三个列表 : ① ② ③
merge不为空,取出第一个列表列表①的表头E,进行判断
各个列表的表尾分别是[O], [E,F,O],E在这些表尾的集合中,因而跳过当前当前列表
取出列表②的表头C,进行判断
C不在各个列表的集合中,因而将C拿出到merge外,并从所有表头删除
merge( [E,O], [C,E,F,O], [C]) = [C] + merge( [E,O], [E,F,O] )
进行下一次新的merge操作 ......
计算mor(A)方式:
mro(A) = mro( A(B,C) )
原式= [A] + merge( mro(B),mro(C),[B,C] )
mro(B) = mro( B(D,E) )
= [B] + merge( mro(D), mro(E), [D,E] ) # 多继承
= [B] + merge( [D,O] , [E,O] , [D,E] ) # 单继承mro(D(O))=[D,O]
= [B,D] + merge( [O] , [E,O] , [E] ) # 拿出并删除D
= [B,D,E] + merge([O] , [O])
= [B,D,E,O]
mro(C) = mro( C(E,F) )
= [C] + merge( mro(E), mro(F), [E,F] )
= [C] + merge( [E,O] , [F,O] , [E,F] )
= [C,E] + merge( [O] , [F,O] , [F] ) # 跳过O,拿出并删除
= [C,E,F] + merge([O] , [O])
= [C,E,F,O]
原式= [A] + merge( [B,D,E,O], [C,E,F,O], [B,C])
= [A,B] + merge( [D,E,O], [C,E,F,O], [C])
= [A,B,D] + merge( [E,O], [C,E,F,O], [C]) # 跳过E
= [A,B,D,C] + merge([E,O], [E,F,O])
= [A,B,D,C,E] + merge([O], [F,O]) # 跳过O
= [A,B,D,C,E,F] + merge([O], [O])
= [A,B,D,C,E,F,O]
结果OK. 那既然python提供了. 为什么我们还要如此⿇烦的计算MRO呢? 因为笔试…….你在笔试的时候, 是没有电脑的. 所以这个算法要知道. 并且简单的计算要会. 真实项⽬开发的时候很少有⼈这么去写代码.
这个说完了. 那C3到底怎么看更容易呢? 其实很简单. C3是把我们多个类产⽣的共同继承留到最后去找. 所以. 我们也可以从图上来看到相关的规律. 这个要⼤家⾃⼰多写多画图就能感觉到了. 但是如果没有所谓的共同继承关系. 那⼏乎就当成是深度遍历就可以了