# 继承 子类可复用父类的属性和方法,并扩展新功能,实现代码复用和逻辑分层。例如,"电动车"类继承自"汽车"类,新增"充电"方法。 不用继承创建对象 ```python 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 ``` 使用继承的方式 ```python class Aniaml(object): def __init__(self,name, age): self.name = name self.age = age def eat(self): print(f"{self.name}吃东西..") class Dog(Aniaml): pass xiaotianquan = Dog("哮天犬",5) xiaotianquan.eat() ``` 继承概念:子类拥有父类的所有方法和属性。 img-继承对比图示 **继承的优点**: 1. 增加了类的耦合性(耦合性不宜多,宜精)。 2. 减少了重复代码。 3. 使得代码更加规范化,合理化。 # 继承分类 上面的那个例子,涉及到的专业术语: - `Dog` 类是 `Animal` 类的**子类**,`Animal` 类是 `Dog` 类的**父类**,`Dog` 类从 `Animal` 类**继承** - `Dog` 类是 `Animal` 类的**派生类**,`Animal` 类是 `Dog` 类的**基类**,`Dog` 类从 `Animal` 类**派生** 继承:可以分**单继承,多继承**。 这里需要补充一下 python 中类的种类(继承需要): 在 python 2 版本中存在两种类.: - ⼀个叫**经典类**. 在 Python2 中,经典类是指没有显式继承自 `object` 的类。它们使用旧的类定义方式。 - ⼀个叫**新式类**. 新式类是指显式继承自 `object` 或其他新式类的类。新式类在 Python 2.2 中引入,并在 Python 3 中成为默认。 # 单继承 ## 对象执行父类方法 ```python 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) class Person(Aniaml): pass class Cat(Aniaml): pass class Dog(Aniaml): pass print(Person.type_name) Person.eat('东西') print(Person.type_name) p1 = Person('aaron','男',18) print(p1.__dict__) print(p1.type_name) p1.type_name = '666' print(p1) p1.eat() ``` ## 执行顺序 ```python class Aniaml(object): def __init__(self,name, age): self.name = name self.age = age def eat(self): print(f"{self.name}吃东西..") class person(Aniaml): def eat(self): print('%s 用筷子吃饭' % self.name) class Dog(Aniaml): pass class Cat(Aniaml): pass person1 = person('张三',18) person1.eat() ``` ## 同时执行类以及父类方法 方法一:如果想执行父类的 eat() 方法,可以在子类的方法中写上:父类.eat(对象,其他参数) ```python 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): Aniaml.__init__(self,name,sex,age) self.mind = mind def eat(self): Aniaml.eat(self) print('%s 吃饭'%self.name) class Cat(Aniaml): pass class Dog(Aniaml): pass p1 = Person('aaron','男',18,'想吃东西') p1.eat() ``` 方法二:利用 `super().func(参数)` ```python 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): # 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('aaron','男',18,'想吃东西') p1.eat() ``` ## 方法重写 - 如果在开发中,父类的方法实现和子类的方法实现,完全不同,可以使用覆盖的方式,在子类中重新编写父类的方法实现 - 具体的实现方式,就相当于在子类中定义了一个和父类同名的方法并且实现 - 重写之后,在运行时,只会调用子类中重写的方法,而不再会调用父类封装的方法 ## 子类中扩展父类方法 - 如果在开发中,子类的方法实现中包含父类的方法实现 - 父类原本封装的方法实现** 是 **子类方法的一部分** - 就可以使用扩展的方式 1. 在子类中重写父类的方法 2. 在需要的位置使用 `super().父类方法` 来调用父类方法的执行 3. 代码其他的位置针对子类的需求,编写 子类特有的代码实现 **关于 `super`** - 在 `Python` 中 `super` 是一个特殊的类 - `super()` 就是使用 `super` 类创建出来的对象 - 最常使用的场景就是在重写父类方法时,调用在父类中封装的方法实现 ## 父类的私有属性和私有方法 子类对象不能在自己的方法内部,直接访问父类的私有属性或私有方法,但可以通过父类的公有方法间接访问 - **私有属性、方法** 是对象的隐私,不对外公开,外界以及子类都不能直接访问 - **私有属性、方法** 通常用于做一些内部的事情 ![img-父类的私有属性和私有方法](继承与多态/父类的私有属性和私有方法.png) - `B` 的对象不能直接访问 `__num2` 属性 - `B` 的对象不能在 `demo` 方法内访问 `__num2` 属性 - `B` 的对象可以在 `demo` 方法内,调用父类的 `test` 方法 - 父类的 `test` 方法内部,能够访问 `__num2` 属性和 `__test` 方法 ```python class Animal: def __init__(self,name): self.__name = name def __eat(self): print(self.__name + "Eating...") def eat2(self): self.__eat() class Dog(Animal): pass a = Dog('哮天犬') print(a.name) a.__eat() a.eat2() ``` # 多继承 **概念** - **子类** 可以拥有 **多个父类**,并且具有 **所有父类** 的 **属性** 和 **方法** - 例如:**孩子** 会继承自己 **父亲** 和 **母亲** 的 **特性** ![img-多继承1](继承与多态/多继承1.png) **语法** ```python class 子类名(父类名1, 父类名2...) pass ``` **问题的提出** - 如果 **不同的父类** 中存在 **同名的方法**,**子类对象** 在调用方法时,会调用 **哪一个父类中**的方法呢? > 提示:**开发时,应该尽量避免这种容易产生混淆的情况!** —— 如果 **父类之间** 存在 **同名的属性或者方法**,应该 **尽量避免** 使用多继承 ![img-多继承2](继承与多态/多继承2.png) ```python class shengxian: # 神仙 def fei(self): print("神仙会飞") def eat(self): print("吃人参果") class monkey: # 猴 def eat(self): print("吃桃子") class songwukong(shengxian,monkey): #孙悟空既是神仙也是猴 def __init__(self): self.name = "孙悟空" def eat(self): print("我是齐天大圣,我不用吃东西") swk = songwukong() swk.eat() ``` ## 经典类 ```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 ``` 示意图 ![img-经典类多继承](继承与多态/经典类多继承.png) 在经典类中采⽤的是深度优先,遍历⽅案:优先遍历继承链的最左侧分支直至顶端,再向右查找其他分支。 类的 MRO (method resolution order): Foo-> H -> G -> F -> E -> D -> B -> A -> C。 ## 新式类 C3 算法是 Python 中用于解决多继承场景下方法解析顺序(MRO)的核心算法,其设计目标是保证继承关系的​​一致性​​、​​本地优先级​​和​​单调性​​。 ### 算法核心原理 C3 算法的核心是​**​线性化合并规则**​​,通过递归合并父类的MRO列表生成一个无冲突的继承顺序链。 对于类 `C(B1, B2, ..., Bn)` ,其 MRO 计算公式为为:`L[C] = C + merge(L[B1],L[B2],..,L[Bn],[B1,B2,...,Bn])` 其中 `megre` 操作负责合并父类的 MRO 序列。 ### `merge` 合并规则 `merge` 操作是 C3 算法的核心步骤,具体执行流程如下: 1. **​选取第一个列表的头元素(Head)​​:** 即列表的第一个元素,记为 `h`。 2. **​检查 `h` 的有效性:** - 若 `h` 不在其他列表的​​尾部​​(即其他列表除首元素外的部分),则将其加入结果序列,并从所有列表中删除 `h`。 - 若 `h` 存在于其他列表的尾部,则跳过当前列表,选择下一个列表的头元素重复检查。 3. **​递归执行​​:** 重复步骤1-2,直到所有列表为空(成功)或无法找到有效头元素(失败并抛出 `TypeError`) **示例解析** 假设类 `D` 继承自 `B` 和 `C`,其父类的 MRO 分别为 `L(B)=[B, A, O]` 和 `L(C)=[C, A, O]`,则 `D` 的 MRO 计算如下: `L(D) = D + merge([B,A,O],[C,A,O],[B,C])` 1. 初始合并列表为 `[[B, A, O], [C, A, O], B, C]`。 2. 提取 `B`(不在其他列表尾部),结果序列为 `[D, B]`,剩余列表 `[[A, O], [C, A, O], C]`。 3. 提取 `C`(不在其他列表尾部),结果序列扩展为 `[D, B, C]`,剩余列表 `[[A, O], [A, O]]`。 4. 合并 `A` 和 `O`,最终得到 `L(D)=[D, B, C, A, O]` ### 实践案例 ![img-新式类megre](继承与多态/新式类megre.png) 计算 mro(A) 方式: step1: L(A) = A + merge(L[B] + L[C], [B, C]) L(B) = B + merge(L[D] + L[E], [D, E]) L(C) = C + merge(L[E] + L[F], [E, F]) L(D) = D + merge(L[O]) = [D, O] L(E) = E + merge(L[O]) = [E, O] L[F] = F + merge(L[O]) = [F, O] step2: L(B) = B + merge([D, O], [E, O], [D, E]) = [B, D] + merge([O], [E, O], [E]) # 拿出并删除D = [B, D, E] + merge([O], [O]) # 拿出并删除E = [B, D, E, O] L(C) = C + merge([E, O], [F, O], [E, F]) ... = [C, E, F, O] step3: L(A) = A + merge([B, D, E, O], [C, E, F, O], [B, C]) = [A, B] + merge([D, E, O], [C, E, F, O], [C]) # h = B 拿出并删除B = [A, B, D] + merge([E, O], [C, E, F, O], [C]) # h = D 拿出并删除D = [A, B, D] + merge([E, O], [C, E, F, O], [C]) # h = E 存在于其他列表的尾部则跳过 ... = [A, B, D, C, E, F, O] **代码** ```python class D: pass class E: pass class F: pass class B(D,E): pass class C(E,F): pass class A(B,C): pass print(A.__mro__) ``` # 多态 同一方法在不同对象中呈现不同行为,增强代码灵活性。例如,"动物"类的"发声"方法在"狗"和"猫"对象中分别输出"汪汪"和"喵喵"。 - 多态可以增加代码的灵活度 - 以继承和重写父类方法为前提 - 是调用方法的技巧,不会影响到类的内部设计 ![img-多态示意图](继承与多态/多态示意图.png) ```python class human(object): def work(self): return "喝杯咖啡,开始工作" class ps_job(human): def work(self): return "开始美工" class IT_job(human): def work(self): return "开始敲代码" def job(person): # 多态函数 print(person.work()) # 创建不同类型的对象 ps = ps_job() it = IT_job() # 调用同一个函数,表现出不同的行为 job(ps) job(it) ``` **案例:哮天犬** **需求** 1. 在 `Dog` 类中封装方法 `game` 2. 定义 `XiaoTianDog` 继承自 `Dog` ,并且重写 `game` 方法 3. 定义 `Person` 类,并且封装一个和狗玩的方法 ![img-多态](继承与多态/多态.png) **实现** ```python class Dog(object): def __init__(self, name): self.name = name def game(self): print("%s 蹦蹦跳跳的玩耍..." % self.name) class XiaoTianDog(Dog): def game(self): print("%s 飞到天上去玩耍..." % self.name) class Person(object): def __init__(self, name): self.name = name def game_with_dog(self, dog): print("%s 和 %s 快乐的玩耍..." % (self.name, dog.name)) # 让狗玩耍 dog.game() # 1. 创建一个狗对象 wangcai = Dog("旺财") xiaotianquan = XiaoTianDog("飞天旺财") # 2. 创建一个小明对象 xiaoming = Person("小明") # 3. 让小明调用和狗玩的方法 xiaoming.game_with_dog(wangcai) xiaoming.game_with_dog(xiaotianquan) ``` **案例小结** - `Person` 类中只需要让狗对象调用 `game` 方法,而不关心具体是什么狗。 - `game` 方法是在 `Dog` 父类中定义的。 - 在程序执行时,传入不同的狗对象实参,就会产生不同的执行效果。 # 鸭子类型 python 中有一句谚语说的好,你看起来像鸭子,那么你就是鸭子。 这句谚语是关于鸭子类型(Duck Typing)的一种表达方式。鸭子类型是一种动态类型的概念,它强调一个对象的特征和行为,而不是其具体的类型或继承关系。 在 Python 中,鸭子类型的概念可以简单地表述为:如果一个对象具有像鸭子一样的特征和行为,那么我们可以认为它是一个鸭子。这意味着我们关注对象是否具备特定的方法和属性,而不关心对象的具体类型。 这种思想在 Python 中经常被使用,特别是在函数参数传递和对象的使用上。如果一个函数接受一个参数,并假设该参数具有某些特定的方法或属性,那么只要传递的对象满足这些要求,它就可以正常工作,无论对象的具体类型是什么。 下面是一个简单的示例来说明鸭子类型的概念: ```python class Duck: def quack(self): print("嘎嘎叫!") def fly(self): print("扑哧扑哧的飞!") class Person: def quack(self): print("我喜欢跟鸭子一样嘎嘎叫") def fly(self): print("我也喜欢跟鸭子一样飞") def make_it_quack_and_fly(obj): obj.quack() obj.fly() duck = Duck() person = Person() make_it_quack_and_fly(duck) make_it_quack_and_fly(person) ``` 在上述示例中,我们定义了一个 `Duck` 类和一个 `Person` 类,它们都具有 `quack` 和 `fly` 方法。然后,我们定义了一个函数 `make_it_quack_and_fly`,它接受一个参数 `obj`,并调用 `obj` 的 `quack` 和 `fly` 方法。 我们可以看到,无论是 `Duck` 对象还是 `Person` 对象,只要它们具有 `quack` 和 `fly` 方法,都可以作为参数传递给 `make_it_quack_and_fly` 函数,并成功执行相应的方法。 这正是鸭子类型的思想:如果一个对象具有像鸭子一样的特征和行为(即具有 `quack` 和 `fly` 方法),那么我们可以将其视为鸭子,而无需关心对象的具体类型。 # 类的约束 写一个支付功能 ```python class QQpay: def pay(self,money): print('使用qq支付%s元' % money) class Alipay: def pay(self,money): print('使用阿里支付%s元' % money) a = Alipay() a.pay(100) b = QQpay() b.pay(200) ``` 统一一下付款方式 ```python class QQpay: def pay(self,money): print('使用qq支付%s元' % money) class Alipay: def pay(self,money): print('使用阿里支付%s元' % money) def pay(obj,money): obj.pay(money) a = Alipay() b = QQpay() pay(a,100) pay(b,200) ``` 如果后期添加微信支付,但是没有统一标准,换个程序员就可能写成这样。 ```python class QQpay: def pay(self,money): print('使用qq支付%s元' % money) class Alipay: def pay(self,money): print('使用阿里支付%s元' % money) class Wechatpay: def fuqian(self,money): print('使用微信支付%s元' % money) def pay(obj,money): print("===============") obj.pay(money) a = Alipay() b = QQpay() pay(a,100) pay(b,200) c = Wechatpay() c.fuqian(300) ``` 所以此时我们要用到对类的约束,对类的约束有两种: 1. 提取父类,然后在父类中定义好⽅法,在这个方法中什么都不⽤⼲,就抛⼀个异常就可以了,这样所有的⼦类都必须重写这个⽅法,否则,访问的时候就会报错。 2. 使⽤元类来描述方类,在元类中给出⼀个抽象方法,这样子类就不得不给出抽象方法的具体实现,也可以起到约束的效果。(推荐) **方法1** ```python class Payment: """ 此类什么都不做,就是制定一个标准,谁继承我,必须定义我里面的方法。 """ def pay(self,money): raise Exception("你没有实现pay方法") class QQpay(Payment): def pay(self,money): print('使用qq支付%s元' % money) class Alipay(Payment): def pay(self,money): print('使用阿里支付%s元' % money) class Wechatpay(Payment): def fuqian(self,money): print('使用微信支付%s元' % money) def pay(obj,money): obj.pay(money) a = Alipay() b = QQpay() c = Wechatpay() pay(a,100) pay(b,200) pay(c,300) ``` **方法2:引入抽象类的概念处理 ```python from abc import ABCMeta,abstractmethod class Payment(metaclass=ABCMeta): # 抽象类 接口类 规范和约束 metaclass 指定的是一个元类 @abstractmethod def pay(self):pass # 抽象方法 class Alipay(Payment): def pay(self,money): print('使用支付宝支付了%s元'%money) class QQpay(Payment): def pay(self,money): print('使用qq支付了%s元'%money) class Wechatpay(Payment): # def pay(self,money): # print('使用微信支付了%s元'%money) def recharge(self):pass def pay(a,money): a.pay(money) a = Alipay() a.pay(100) pay(a,100) # 归一化设计:不管是哪一个类的对象,都调用同一个函数去完成相似的功能 q = QQpay() q.pay(100) pay(q,100) w = Wechatpay() pay(w,100) # 到用的时候才会报错 # 抽象类和接口类做的事情 :建立规范 # 制定一个类的metaclass是ABCMeta, # 那么这个类就变成了一个抽象类(接口类) # 这个类的主要功能就是建立一个规范 ``` # `super()` 深入了解 super 是严格按照类的继承顺序执行。 **示例1** ```python class A: def f1(self): print('in A f1') def f2(self): print('in A f2') class Foo(A): def f1(self): super().f2() print('in A Foo') obj = Foo() obj.f1() ``` **示例2** ```python class A: def f1(self): print('in A') class Foo(A): def f1(self): super().f1() print('in Foo') class Bar(A): def f1(self): print('in Bar') class Info(Foo,Bar): def f1(self): super().f1() print('in Info f1') obj = Info() obj.f1() # super() 是严格按照当前类的继承顺序执行的,不会收到过程中其他类的影响 print(Info.mro()) ``` **示例3** ```python class A: def f1(self): print('in A') class Foo(A): def f1(self): super().f1() print('in Foo') class Bar(A): def f1(self): print('in Bar') class Info(Foo,Bar): def f1(self): super(Foo,self).f1() # 这里的意思是绕过Foo,从Foo的位置开始寻找下一个 print('in Info f1') obj = Info() obj.f1() ```