32、面向对象之继承

面向对象 / 2020-12-31

一、继承

1.1、何为继承?

继承是一种创建新类的方式,新建的类可称为子类或派生类,父类别称基类或超类!Python支持多继承,子类可继承一个或多个父类,同样的,子类会继承父类的属性!

1.1.1、多继承

优点:子类可以同时继承多个父类的属性遗传,最大限度地提高代码重复使用率!

缺点:违背了人的思维习惯,代码可读性变差,扩展性变差,可能会引发恶性的菱形问题!非要用 应该使用mixins

1.1.2、新式类与经典类

在Python3中,所有类都默认继承object类,就好像无论是欧洲人、亚洲人、美洲人都是人一样,这样的类我们称之为新式类!在Python2中,默认不继承object的类及其子类,我们称之为经典类!当然了,Python2你也可以指定继承object,这样就变新式类了!

两者有什么区别?请听我后面慢慢分解!

1.2、为何用继承?

据上所述,子类会继承父类的属性,也意味着减少类与类之间的代码冗余!

1.3、如何用继承?

1.3.1、查看继承关系

bases方法可以查看继承关系

class School:  # 父类
    ...

class Guangzhou:  # 父类
    ...

class Student(School):  # 单继承
    ...

class Teacher(School, Guangzhou):  # 多继承
    ...
    
print(Teacher.__bases__)  # 查看继承关系

# 执行得到结果
(<class '__main__.School'>, <class '__main__.Guangzhou'>)

二、继承与抽象

将对象抽象化,提炼其相似之处即可分门归类;提炼类之间的相似之处即可得到父类!我画了一张图来阐述:

image.png

三、属性查找

3.2、单继承

对象查找属性时,先从自身找起,无则去类中找,再无则去父类中找,如此类推!

3.3、多继承

多继承情况下,查找前提还是跟单继承一样,但是多继承很有可能引发菱形问题(有兴趣自己网上搜索相关文章),比如说一个套一个,套的还是同一个类,那该用谁?因此Python给定义的每一个类计算出一个查找顺序表:MRO,查找属性时会依照这个表的顺序一直找下去!以上面例子为准:

print(Teacher.__mro__)
# 执行得到结果
(<class '__main__.Teacher'>, <class '__main__.School'>, <class '__main__.Guangzhou'>, <class 'object'>)

总的来说,发起属性查找,会先从自身找起,无则按照mro列表来找!

四、Mixins机制

Python独特的多继承很容易使人搞混,代码可读性大大降低!因此Python提供了一种规范:Mixins,当需要混合不同类的功能时,这些被此采用的类使用统一的命名规范,比如Mixins结尾,Mixins应该具备以下条件:

4.1、Mixins规范

1、以Mixins结尾的名称应该是代表着某种功能,比如说writeMixins,而不是某种物品或者说数据属性,如bookMixins!

2、功能应该是单一的,如果需要混用多个功能,那就分开写多个Mixins类!

3、不依赖于其它类,即不继承其它类!

4、子类即使没继承Mixins类,也能正常执行!

class Animal:  # 父类
    ...

class Cattle(Animal):
    ...

class swimMixins:  # 游泳功能
    print('I can swim')

class Fish(swimMixins, Animal):  # 父类是Animal,swimMixins仅作为一种功能加入
    ...

五、派生与重用

派生即在类的基础上衍生新属性,先上车再解释:

class School:
    school = 'Python'

    def __init__(self, name, age):
        self.name = name
        self.age = age

class Student(School):
    def __init__(self, name, age, gender):
        self.name = name
        self.age = age
        self.gender = gender
        
class Teacher(School):
    def __init__(self, name, age, gender, salary):
        self.name = name
        self.age = age
        self.gender = gender
        self.salary = salary

派生即在类的基础上衍生新属性,上面的例子很明显代码冗余了!但是student和teacher又比父类多出那么一点不一样,能不能较少重复的那几行代码?

5.1、代码重用

在派生的子类中如何重用父类的功能?

方式一:直接调用

直接调用某一个类下的函数,而不依赖于继承关系

class Student():
    def __init__(self, name, age, homework):
        School.__init__(self, name, age)  # 不依赖继承,因此必须手动传入对象self
        self.homework = homework
方式二:super()

调用super会得到一个特殊对象super,该对象专用于引用父类属性!此方法依赖于继承关系,且属性查找顺序严格依照mro!

class Teacher(School):
    def __init__(self, name, age, salary):
        super().__init__(name, age)  # 依赖继承,因此不需要手动传入对象
        self.salary = salary

5.2、组合

以下内容均转载自知乎egon老师第六节:组合,链接地址:https://zhuanlan.zhihu.com/p/109331525

在一个类中以另外一个类的对象作为数据属性,称为类的组合。组合与继承都是用来解决代码的重用性问题。不同的是:继承是一种“是”的关系,比如老师是人、学生是人,当类之间有很多相同的之处,应该使用继承;而组合则是一种“有”的关系,比如老师有生日,老师有多门课程,当类之间有显著不同,并且较小的类是较大的类所需要的组件时,应该使用组合,如下示例

class Course:
    def __init__(self,name,period,price):
        self.name=name
        self.period=period
        self.price=price
    def tell_info(self):
        print('<%s %s %s>' %(self.name,self.period,self.price))

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类和Course类的代码
class Teacher(People): #老师是人
    def __init__(self,name,sex,age,title,year,mon,day):
        super().__init__(name,age,sex)
        self.birth=Date(year,mon,day) #老师有生日
        self.courses=[] #老师有课程,可以在实例化后,往该列表中添加Course类的对象
    def teach(self):
        print('%s is teaching' %self.name)


python=Course('python','3mons',3000.0)
linux=Course('linux','5mons',5000.0)
teacher1=Teacher('lili','female',28,'博士生导师',1990,3,23)

# teacher1有两门课程
teacher1.courses.append(python)
teacher1.courses.append(linux)

# 重用Date类的功能
teacher1.birth.tell_birth()

# 重用Course类的功能
for obj in teacher1.courses: 
    obj.tell_info()

此时对象teacher1集对象独有的属性、Teacher类中的内容、Course类中的内容于一身(都可以访问到),是一个高度整合的产物

世间微尘里 独爱茶酒中