继承与派生
在python中,新建的类可以继承一个或多个父类(有些语言不能继承多个父类),这种类可以被称为子类或派生类,父类则可以被称为基类或超类
经典类与新式类
在python2中有经典类与新式类之分,然而在python3中则没有这种区分,统一为新式类。新式类与经典类最大的区别就是有无继承object类,在python3中不论是否显示继承object类,默认都是继承object类的
class Parent:
pass
class Sub(Parent):
pass
print(Sub.__bases__)
print(Parent.__bases__)
>>>(<class '__main__.Parent'>,)
>>>(<class 'object'>,)
至于新式类与继承类的区别我们稍后再谈
继承与抽象
使用父类与子类究其原因是因为我们需要减少代码的冗余,我们将多个类中的重复功能或者代码提取出来,把他拼成一个父类,然后我们再通过继承父类去减少代码的冗余。而这个提取的过程就是抽象。
同时我们需要了解一个类与一个类如果是继承的关系,那么他们两者之间必须遵守的是a is b 的关系 b是父类 a是子类 在后文中提到的组合则是a have b的关系
父类与子类的寻找顺序
class Teacher:
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
class Student(Teacher):
def test(self):
print(self.name)
student = Student('施',18,'男')
student.test()
如上所示,若我们没有再字类中定义 init 函数,他会从父类中进行寻找。
class Teacher:
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
def test(self):
print(self.age)
class Student(Teacher):
def test(self):
print(self.name)
print(self.age)
student = Student('施',18,'男')
student.test()
>>>施
>>>18
如上所示,若是再父类与字类中有冲突的方法的话,将会优先寻找自己本身中的方法
因此综上所述。若你定义了一个字类的函数,并且再父类中有相同的方法那么会优先再本身中去找,找不到则去父类中找
菱形问题
大多数语言都不支持多继承,python则不一样,一个子类可以继承多个父类,这固然可以解决代码的重用问题,但是也有可能引发著名的 Diamond problem菱形问题
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W2eRQBmo-1597025837043)(C:\Users\QAQ\Desktop\python学习\图片\面向对象\继承与派生\钻石菱形问题.png)]
如上图,这就是钻石菱形问题。
它的含义是当b,c当中同时有相同的方法,那么d应该继承谁的那个方法呢?
而python是怎么解决这个问题的呢?
深度优先与广度优先
python中有两种寻找的方法
1.深度优先
在经典类中才有的也就是该方法
class A:
pass
class B(A):
pass
class C(A):
pass
class D(B):
pass
class E(C):
pass
class F(D):
pass
class G(E):
pass
class H(G,F):
pass
#例如该种形式,最后所有的函数都会继承A类
#他们的寻找顺序则是G->E->C->A然后再去找另一条路线
2.广度优先
class A:
pass
class B(A):
pass
class C(A):
pass
class D(B):
pass
class E(C):
pass
class F(D):
pass
class G(E):
pass
class H(G,F):
pass
#例如该种形式,最后所有的函数都会继承A类
#广度优先的寻找顺序则是G->E->C然后再去找另一条路线,最后再去找A
综上所述:新式类与经典类的最大区别就是解决菱形问题的方式不一样,其余大致一致
python Mixins机制
在现实中我们是极为不提倡使用多继承的方式去解决代码重用的问题的,因为 首先这样的设计可能会导致菱形问题 , 其次与我们的设计的逻辑也往往有一些冲突,但是在编写代码的时候往往会出现一些多个类有重复的代码,但是放在上面的父类却又不适合的情况。
在java中提供了接口方法去解决这个情况。
而在python中并没有接口的这种概念,因此他规定在类方法后加 Mixin以用来区分父类与普通接口的情况
class RunMixin:
def run(self):
pass
pass
class Animal:
pass
class Fish(Animal):
pass
class Dog(Animal,RunMixin):
pass
class Cat(Animal,RunMixin):
pass
当然这些接口与我们平时见的类也并无本质区别,加的后缀也更像是一种约定俗称的做法。
这主要也是与python的设计理念有很大的关系
使用Mixin类实现多重继承需要非常小心
1.首先使用Mixin它必须代表的是某一种功能,而不是一种物品,不然不符合 “is-a”原则
2.一个Mixin只写一种方法,对于多个方法我们可以采用使用多个Mixin来解决代码冗余问题
3.它不依赖于子类的实现
4.子类即使没有继承这个类,也不会影响工作
Mixin确实在很大程度上解决了我们的问题,但是也有一定的缺陷
当我们定义了多个Mixin时我们很容易陷入代码的可读性下降得情况
派生与方法重用
派生:本指江河的源头产生出支流。现引申为从一个主要事物的发展中分化出来,这里指的是子类扩展出自己的新方法或属性
由上文的父类与子类的寻找顺序我们可以知道,寻找顺序会优先从子类中寻找,因此当我们的子类需要扩展一些方法或属性时,我们新写的方法与属性就会覆盖原父类的方法或属性。
那当我们需要调用父类中的属性或方法时该怎么做呢。
在python中有两种方法
1.直接使用 父类名.方法名或函数名
class People:
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
class Teacher(People):
def __init__(self,name,age,sex,address):
People.__init__(self,name,age,sex)
self.address = address
#因为是直接调用因此需要几个参数就给几个参数,self不能忘记
2.super()
class People:
def __init__(self,name,age,sex):
self.name = name
self.age = age
self.sex = sex
class Teacher(People):
def __init__(self,name,age,sex,address):
#自动对父类传入self,并且严格遵守MRO规定
super().__init__(name,age,sex)
self.address = address
print(name,sex,address,age)
teacher = Teacher('施',18,'male','萧山')
该两种方式的区别是第一种是不依赖于继承关系的,第二种则是依赖于继承的,这两种方式都可以使用,但是建议使用第二种
组合
上面说到继承是一种“a is b"的关系,那么组合就是一种”a 有 b“ 的关系
class Date:
def __init__(self,year,mon,day):
self.year=year
self.mon=mon
self.day=day
def tell_birth(self):
print('<%s-%s-%s>' %(self.year,self.mon,self.day))
class People:
school='清华大学'
def __init__(self,name,sex,age):
self.name=name
self.sex=sex
self.age=age
#Teacher类基于继承来重用People的代码,基于组合来重用Date类
class Teacher(People): #老师是人
def __init__(self,name,sex,age,title,year,mon,day):
super().__init__(name,age,sex)
self.birth=Date(year,mon,day) #老师有生日
def teach(self):
print('%s is teaching' %self.name)
teacher = Teacher('施','male','18','1','2019','10','1')
多态性
多态与多态性
多态:一种事物的多种形态,反应到面向对象中指的是父类中的一种方法,在不同的子类中展示出不同的形态。
class Animal:
def bark(self):
pass
pass
class Dog(Animal):
def bark(self):
print('汪汪汪')
class Cat(Animal):
def bark(self):
print('喵喵喵')
如上述代码所示动物的形态之一就是狗,形态之二就是猫
这体现的就是一种多态
而多态性指的就是在不同类中虽然形态不一样,但是拥有相同的方法,这就要求我们在设计的时候就需要把对象的使用方法统一成一种
类似java接口的方法
你若是需要使底下的各个类中的方法统一名称,你可以导入abc模块
import abc
class Animal(metaclass=abc.ABCMeta):
@abc.abstractmethod
def bark(self):
pass
pass
class Dog(Animal):
def talk(self):
pass
class Cat(Animal):
def bark(self):
print('喵喵喵')
dog = Dog()
dog.talk()
#这样就会报错
>>>TypeError: Can't instantiate abstract class Dog with abstract methods bark
但是在python中并不推荐这种方式。
鸭子类型
python认为这种方式会有继承的一些弊端。他更推崇于程序员自身在设计以及编写代码时就自己自发的将方法名称进行统一。及不使用继承的方式,这种也被称为鸭子类型(看起来是鸭子那他就是鸭子)
class Dog:
def dark(self):
print('汪汪汪')
pass
class Cat:
def bark(self):
print('喵喵喵')
dog = Dog()
dog.dark()