我 的 个 人 主 页:👉👉 失心疯的个人主页 👈👈
入 门 教 程 推 荐 :👉👉 Python零基础入门教程合集 👈👈
虚 拟 环 境 搭 建 :👉👉 Python项目虚拟环境(超详细讲解) 👈👈
PyQt5 系 列 教 程:👉👉 Python GUI(PyQt5)文章合集 👈👈
Oracle数据库教程:👉👉 Oracle数据库文章合集 👈👈
优 质 资 源 下 载 :👉👉 资源下载合集 👈👈
这里写目录标题
面相对象三大特性
封装
封装的概念
- 将一些属性和相关方法封装在一个对象中,对外隐藏内部具体实现细节
- 类似于【电脑主机】,内部实现,外界不需要关心,外界只需要根据“内部提供的接口”去使用就可以
封装的好处
- 1、使用起来更加方便
- 把很多相关的功能封装成一个整体
- 类似于向外界提供一个工具箱
- 针对不同的场景,使用不同的工具箱就可以
- 2、保证数据的安全性
- 针对安全级别高的数据,可以设置成“私有”
- 可以控制数据位只读,外界无法修改
- 也可以拦截数据的写操作,进行数据验证和过滤
- 3、利于代码维护
- 如果后期,功能代码需要维护,则直接修改这个类内部代码即可
- 只要保证接口名称不变,外界则不需要做任何代码修改
- 前面讲的[私有化属性]、[只读属性]都是封装的体现
继承
继承的概念
- 一个类“拥有”另外一个类的“资源”的方式之一
- 拥有:并不是资源的复制,而是资源的“使用权”
- 资源:指“非私有的”属性和方法
- 继承关系中类的名称
- 被继承的类:父类、超类、基类
- 继承的类:子类、派生类
继承的目的
* 方便资源的重用
继承的分类
单继承
- 概念
- 仅仅继承了一个父类
- 语法
class Animal: pass # 单继承 class Dog(Animal): pass
多继承
- 概念
- 继承了多个父类
- 语法
class Animall: pass class Dog: pass # 多继承 class keji(Dog, Animall): pass
type类和object类
-
查看取指定类的父类(基类)
cls.__bases__
-
查看制定对象由哪个类创建(实例化)
- 类的本身也是对象
obj.__class__
-
示例
class Animal: pass class Dog: pass class Keji(Animal, Dog): pass d = Dog() d.eat = '肉' # 查看制定类的父类 print(Keji.__bases__) # (<class '__main__.Animal'>, <class '__main__.Dog'>) print(Dog.__bases__) # (<class 'object'>,) print(Animal.__bases__) # (<class 'object'>,) print(object.__bases__) # () print(int.__bases__) # (<class 'object'>,) print(float.__bases__) # (<class 'object'>,) print(str.__bases__) # (<class 'object'>,) print(bool.__bases__) # (<class 'int'>,) # 查看对象由谁创建 print(d.__class__) # <class '__main__.Dog'> print(d.eat.__class__) # <class 'str'> print(Keji.__class__) # <class 'type'> print(Dog.__class__) # <class 'type'> print(Animal.__class__) # <class 'type'> print(object.__class__) # <class 'type'> print(type.__class__) # <class 'type'> print(int.__class__) # <class 'type'> print(float.__class__) # <class 'type'> print(str.__class__) # <class 'type'> print(bool.__class__) # <class 'type'>
-
关系图
-
复习
- 元类:Type类(创建类对象的类)
- 经典类:不继承自object类的类
- 新式类:继承自object类的类
继承对资源的影响
资源的继承
- 在Python中,继承是指资源额使用权。资源能否被继承,其实就是在子类中能否访问到父类当中的这个资源(属性和方法)
- 类内部的公有属性/方法、受保护属性/方法、内置方法均可被继承,只有私有属性/方法不可以被继承
- 继承仅仅只是访问并不能被设置
公有 # a = 777 # 可以被继承 受保护 # _b = 888 # 可以被继承 私有 # __c = 999 # 不能被继承 内置 # __init__ # 可以被继承
- 查看示例
class Animal: a = 777 _b = 888 __c = 999 def t1(self): print('t1') def _t2(self): print('_t2') def __t3(self): print('__t3') def __init__(self): print('Animal __init__') class Person(Animal): def test(self): print(self.a) # 777 print(self._b) # 888 # print(self.__c) # 报错:AttributeError: 'Person' object has no attribute '_Person__c' self.t1() # t1 self._t2() # _t2 # self.__t3() # 报错:AttributeError: 'Person' object has no attribute '_Person__t3' self.__init__() # Animal __init__ p = Person() p.test()
资源的使用
继承的几种形态
- 单继承链
- 无重叠的多继承
- 有重叠的多继承
资源继承的标准顺序
- 单继承链
def C: pass def B(C): pass def A(B): pass
先从自身(A)内部查找 ——> 到父类(B)内部查找 ——> 到父类的父类(C)内部查找
- 无重叠的多继承(遵循单调原则)
def C2: pass def C1: pass def B2(C2): pass def B1(C1): pass def A(B1, B2): pass
先从自身(A)内部查找 ——> 到左侧父类(B1)内部查找 ——> 到左侧父类的父类(C1)内部查找 ——> 到右侧父类(B2)内部查找 ——> 到右侧父类的父类(C2)内部查找
- 有重叠的多继承
def C1: pass def B2(C1): pass def B1(C1): pass def A(B1, B2): pass
先从自身(A)内部查找 ——> 到左侧父类(B1)内部查找 ——> 到右侧父类(B2)内部查找 ——> 到父类的父类(C1)内部查找
- 继承资源查找顺序图
继承资源查找顺序的演化过程
-
MRO,就是方法解析顺序(Method Resolution Order)。在查找资源时,会对当前类以及所有的基类进行一个搜索,确定资源具体在哪。不管用哪种方式去确定MRO列表
-
Python2.2之前
-
仅存在经典类(不继承自object类)
-
Python2.2之前的MRO:深度优先(从左往右)
-
知识补充
- MRO(Method Resolution Order):方法解析顺序
- 深度优先:沿着一个继承链,尽可能的往深了去找
- 深度优先具体算法步骤
- 1、先把根节点压入栈中
- 2、每次从栈中弹出一个元素,搜索所有在它下一级的元素
- 3、重复第2个步骤,直到搜索不到下一级元素
-
深度优先 继承资源查找顺序图示
-
单继承
-
-
无重叠多继承
-
-
有重叠多继承
-
-
-
问题点
- 在2.2版本之前的这种深度优先的查找顺序模式下,有重叠的多继承在资源查找上就存在问题,违背了“重写可用原则”。
- B2如果重写了C1中的资源,那么通过这种模式拿到的资源,还是C1中的原始资源,并不能拿到最新的B2中重写的资源
-
Python2.2
-
产生了新式类(继承自object类的类)(Python2.2版本开始才有object类)
-
Python2.2的MRO:
- 经典类:深度优先(从左到右)
- 新式类:在深度优先(从左到右)的算法基础上优化了一部分:如果产生重复元素,会保留最后一个,并且更尊重基类出现的先后顺序
-
经典类
- 三种模式的继承资源查找顺序与2.2版本之前一致,就不再做动态图解了
-
新式类
- 单继承模式的继承资源查找顺序与经典类的单继承模式一样,图解省略
- 无重叠继承模式在变成新式类之后,实际上也就变成了有重叠继承模式了,只是最终的父类是object类
-
-
注意点:
-
2.2版本这种查找顺序有点类似“广度优先模式”,但是并不是!
-
稍微了解一下“广度优先模式”
- 1、把根节点放到队列的末尾
- 2、每次从队列的头部取出一个元素,搜索这个元素所有的下一级元素,并将其放到队列的末尾(发现元素已经被处理过,则略过)
- 3、重复第2步,直到队列为空则结束
-
队列的进出原则:先进先出(区别于栈的先进后出原则)
-
单继承模式深度优先于广度优先是没有区别的,区别在于多继承模式
-
多继承模式
- 入栈是先右后左,出栈是先左后右(先进后出)
- 入队列是先左后右,出队列也是先左后右(先进先出)
-
有重叠多继承模式图解
-
-
无重叠多继承模式图解
-
- 这里可以看到广度优先算法与我们2.2版本的算法算出来的结果不一致,所以,2.2版本的算法并不是广度优先算法
-
-
问题
- 2.2版本的算法并非完美的算法,这个算法还存在一些问题
- 无法检测出有问题的继承
- 有可能还会违背“局部优先”的原则
- 问题示例
- 问题继承关系图
-
-
这个继承关系实际上是有问题的,A继承自C,就拥有了B、C、D、object类的所有资源,又继承自B,B、D、object类的资源就重复了,其实A继承自B就是完全多余的,但是2.2版本的算法不会提示有问题,而且还能计算出一个结果
-
违背“局部优先”原则
- 在这个继承关系图中,如果B和C中有一个相同名称的资源,按照上面的计算结果,那么我们通过A获取资源,拿到的就是C里面的资源,违反了先左后右的原则
-
- 2.2版本的算法并非完美的算法,这个算法还存在一些问题
-
Python2.3-2.7
-
Python2.3-2.7的MRO
- 经典类:深度优先(从左到右)
- 新式类:C3算法
-
C3算法具体步骤
-
两个公式:
L(object) = [object]
L(子类(父类1, 父类2)) = [子类] + merge(L(父类1), L(父类2),[父类1, 父类2])
-
公式解析
- “+”:代表合并列表
- merge算法
- 1、merge函数中第一个列表的第一个元素是后续所有列表的第一个元素或者后续所有列表中都没有再出现,则将这个元素合并到最终的解析列表中,并从当前操作的所有列表中删除
- 2、如果不符合,则跳过此元素,查找下一个列表的第一个元素,重复1的判断规则
- 3、如果最终无法把所有元素归并到解析列表,则报错
-
C3算法示例(有重叠多继承)
# C3算法 # 1、merge函数中第一个列表的第一个元素是后续列表的第一个元素或者后续列表中没有再出现, # 则将这个元素合并到最终的解析列表中,并从当前操作的所有列表中删除 # 2、如果不符合,则跳过此元素,查找下一个列表的第一个元素,重复1的判断规则 # 3、如果最终无法把所有元素归并到解析列表,则报错 class D(object): pass # L(D(object)) = [D] + merge(L(object), [object]) # = [D] + merge([object], [object]) # merge函数的第一个列表的第一个元素object是后续列表的第一个元素 # = [D, object] + merge([], []) # 将object这个元素合并到最终的解析列表中,并从当前操作的所有列表中删除 # = [D, object] class B(D): pass # L(B(D)) = [B] + merge(L(D), [D]) # = [B] + merge([D, object], [D]) # merge函数的第一个列表的第一个元素D是后续列表的第一个元素 # = [B, D] + merge([object], []) # 将D这个元素合并到最终的解析列表中,并从当前操作的所有列表中删除 # = [B, D, object] + merge([], []) # merge函数的第一个列表的第一个元素object不是后续列表的第一个元素,但是后续列表中没有再出现 # = [B, D, object] # 将object这个元素合并到最终的解析列表中,并从当前操作的所有列表中删除 class C(D): pass # L(C(D)) = [C] + merge(L(D), [D]) # = [C] + merge([D, object], [D]) # = [C, D] + merge([object], []) # = [C, D, object] + merge([], []) # = [C, D, object] class A(B, C): pass # L(A(B, C)) = [A] + merge(L(B), L(C), [B, C]) # = [A] + merge([B, D, object], [C, D, object], [B, C]) # = [A, B] + merge([D, object], [C,D,object],[C]) # merge函数的第一个列表的第一个元素D,不是后续列表的第一个元素,且在后续列表中存在 # = [A, B, C] + merge([D,object],[D, object], []) # 继续判断merge函数的第二个列表的第一个元素C,是后续列表的第一个元素,将C合并到最终的解析列表中,并从当前操作的所有列表中删除 # = [A, B, C, D] + merge([object],[object]) # 再次判断merge函数的第一个列表的第一个元素D,是后续列表的第一个元素,将D合并到最终的解析列表中,并从当前操作的所有列表中删除 # = [A, B, C, D, object] merge([], []) # 再次判断merge函数的第一个列表的第一个元素object,是后续列表的第一个元素,将object合并到最终的解析列表中,并从当前操作的所有列表中删除 # = [A, B, C, D, object] import inspect # 解析出传入类A的所有基类,并按照mro的顺序排列 print(inspect.getmro(A)) # (<class '__main__.A'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.D'>, <class 'object'>) # A ——> B ——> C ——> D ——> object # 这里获取到的结果与上面C3算法的结果一致
-
针对2.2版本中提到的那个问题继承,我们通过C3算法来测试一下,是否能检测出问题
-
-
示例代码
class D(object): pass # L(D(object)) = [D] + merge(L(object),[object]) # = [D, object] class B(D): pass # L(B(D)) = [B] + merge(L(D), [D]) # = [B] + merge([D, object],[D]) # = [B, D] + merge([objec], []) # = [B, D, object] class C(B): pass # L(C(B)) = [C] + merger(L(B), [B]) # = [C] + merge([B, D, object], [B]) # = [C, B] + merge([D, object], []) # = [C, B, D] + merge([object],[]) # = [C, B, D, object] class A(B, C): pass # L(A(B, C)) = [A] + merge(L(B), L(C), [B, C]) # = [A] + merge([B, D, object], [C, B, D, object], [B, C]) # 执行到这里的时候就会报错 # merge第一个列表的第一个元素B,虽然在第三个列表是第一个,但是在第二个列表中并不是第一个元素,而且在这个列表中也存在,B元素不被处理 # 查询第二个列表的第一个元素C,虽然在第一个列表中不存在,但是在第三个列表中存在,且不是第三个列表的第一个元素,C元素不被处理 # 此时无法再继续执行了,就会报错 # 执行这段代码的结果 # Traceback (most recent call last): # File "E:\StudyCode\Python\13-面向对象\23-继承-2.3~2.7版本C3算法.py", line 67, in <module> # class A(B, C): # TypeError: Cannot create a consistent method resolution # order (MRO) for bases B, C
-
C3算法类似拓扑排序算法,但并不是!大部分情况C3算法与拓扑排序算法的结果一致,但是有个别的情况还是会有差别
-
拓扑排序算法步骤
- 1、找到入度为0的一个节点
- 2、把这个节点输出并删除,并且删除这个节点的出边
- 3、循环执行1和2,直到删除所有节点
-
示例图解1
*
- 这个案例中可以看到,拓扑排序算法得到的结果与C3算法得到的结果是一致的
-
示例图解2
*
-
这个案例在前面我们通过C3算法是无法得到结果(报错),但是这里通过拓扑排序算法是可以得到结果。所以C3算法与拓扑排序算法并不是完全相同
-
Python3.x之后
- Python3.x之后不再有经典类
- Python3.x之后的MRO
- C3算法
-
资源查找总结
-
查看资源查找顺序方法
- 方法1
import inspect inspect.getmro(cls)
- 方法2
cls.__mro__
- 方法3
cls.mro()
- 方法1
资源的覆盖
- 并不真正的覆盖,而是因为资源查找顺序优先级导致的。相同的资源会从查找顺序优先级高的类中取出
- 优先级高的覆盖优先级低的
- 资源的覆盖包括属性的覆盖和方法的重写
- 原理
- 在MRO的资源检索链当中,优先级比较高的类写了一个和优先级比较低的类一样的资源(属性或方法),到时候子类再去获取相关资源,就会优先选择优先级比较高的类的资源,而摒弃优先级比较低的类的资源,造成“资源覆盖”的假象
- 资源覆盖引发的
self
和cls
的问题- 通过哪个类调用的类方法,
cls
就是那个类 - 通过哪个类的实例调用的实例方法,
self
就是那个类实例
- 通过哪个类调用的类方法,
- 示例
class D(object): pass class C(D): def test(self): print(self) @classmethod def test2(cls): print(cls) class B(D): def test(self): print(self) @classmethod def test2(cls): print(cls) class A(B, C): pass B.test2() # <class '__main__.B'> C.test2() # <class '__main__.C'> A.test2() # <class '__main__.A'> b = B() b.test() # <__main__.B object at 0x000001C29940DF70> c = C() c.test() # <__main__.C object at 0x000001C29940DAF0> a = A() a.test() # <__main__.A object at 0x0000027986913B80>
资源的累加
- 概念
- 在一个类的基础之上,增加一些额外的资源
- 子类相比于父类,多一些自己特有的资源
- 在被覆盖的方法基础之上,新增内容
- 在一个类的基础之上,增加一些额外的资源
- 资源
# 属性 实例属性 # 通过:实例.属性 调用 类属性 # 通过:类名.属性 调用 # 方法 实例方法 # 通过:实例.方法() 调用 类方法 # @classmethod装饰,通过:类名.方法() 调用 静态方法 # @staticmethod装饰,通过:类名.方法() 或者 实例.方法() 调用
- 示例1:
- 关于super()函数
class B(object): a = 'ba' def __init__(self): self.b = 'bb' @classmethod def t1(cls): print(cls) print('t1') @staticmethod def t2(): print('t2') class A(B): c = 'ac' # 子类相比于父类,多一些自己特有的资源 def __init__(self): super(A, self).__init__() self.d = 'ad' # 子类相比于父类,多一些自己特有的资源 @classmethod def tt1(cls): # 在被覆盖的方法基础之上,新增内容 super(A, cls).t1() print('tt1') @staticmethod def tt2(): super(A, A).t2() # 在被覆盖的方法基础之上,新增内容 print('tt2') a = A() print(a.__dict__) # {'b': 'bb', 'c': 'ac'} A.t1() A.t2() A.tt1() A.tt2()
多态
概念
- 一个类所延升的多种形态
- 如:一个动物类,可能有多种具化的形态小狗、小猫。我们就说针对这个动物类有多种具化的形态就是多态
- 调用时的多种形态
- 在继承的前提下,使用不同的子类调用父类的同一个方法,产生不同的功能
- 示例
class Animal(object): def jiao(self): pass class Dog(Animal): def jiao(self): print('汪汪汪') class Cat(Animal): def jiao(self): print('喵喵喵') def test(obj): obj.jiao() d = Dog() c = Cat() # test(d) # 汪汪汪 test(c) # 喵喵喵
- 多态在Python中的体现
- 动态类型:不指定变量的类型,根据变量赋值的类型而定。
- 鸭子类型:动态类型的一种风格。只要一个对象会走、会游泳、会叫,那它就可以当做一个鸭子进行处理
- 关注点在于对象的“行为和属性”,而非对象的“类型”
- 在上面的实例代码中,
def test(obj)
这个方法中,我们并不需要去考虑obj
的类型,只要obj
这个对象有jiao
的这个方法或者属性,那就可以成功执行,如果没有则会报错 - 在其他语言(静态语言)中,在使用变量的时候,必须先指定类型
def test(Dog a)
,并且赋值必须与指定的类型一致,否则在编译层面就会直接报错。在运行之前直接先检测这个类型传递是否准确 - 此时,我们在写
test()
方法的时候,参数类型指定成Dog
或者Cat
好像都不行。那么,我们这里将参数类型指定成Animal
类型,在传参的时候就可以传Animall
类本身、Dog
类、Cat
类都可以,这里就体现了类Animal
的多态(多种形态)
- 在上面的实例代码中,
- 在Python中,没有真正意义上的多态,也不需要多态
- 因为Python并不关注对象本身的类型,而是关注这个对象是否有这个行为或属性