10.4.1 继承简介
继承是面向对象编程中的三大特性之一,它指的是建立一个新类,从一个先前已经创建的类中继承其属性和方法,而且可以重新定义或添加新的属性和方法,进而建立类的层次或等级关系。其中,被继承的类称为父类(也可称为基类、超类),而实现继承的类称为子类。说的更简单一些,继承就是通过子类对已存在的父类进行功能扩展。
在软件开发中,类的继承使所建立的软件具有开放性和扩展性,这是信息组织与分类行之有效的方法。它简化了类和对象的创建工作量,提供了类的规范的等级结构,使公共的特性能够共享,进而提高了软件的可重用性。
在Python中,不仅可以实现单继承,还支持多继承。
10.4.2 单继承
在Python中,通过使用小括号实现继承关系。示例代码如下:
# 资源包\Code\chapter10\10.4\1022.py
# 所有类的根类是object,即object类是所有类的父类
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
def myInfo(self):
infomation = (f'我的名字:{self.name},我的年龄:{self.age}')
return infomation
class Teacher(Person):
def __init__(self, name, age, teach):
# super()函数用于调用父类中的方法
super().__init__(name, age)
self.teach = teach
teacher1 = Teacher('Python全栈开发-基础入门', 35, 'Python')
# 调用了继承于父类的myInfo()方法
print(teacher1.myInfo())
上面的程序中,子类在继承父类之后,子类就拥有了父类所有的属性和方法。而通常情况下,子类都会在此基础上,扩展一些新的属性和方法,用于进行功能扩展。
虽然子类从父类继承得来的方法中,大部分可能适合子类的使用,但总有个别的方法,并不能直接照搬父类中的方法,如果不对父类中这部分方法进行修改,子类的对象就无法正常使用。针对这种情况,就需要在子类中重写父类中的方法。示例代码如下:
# 资源包\Code\chapter10\10.4\1023.py
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
def myInfo(self):
infomation = (f'我的名字:{self.name},我的年龄:{self.age}')
return infomation
class Teacher(Person):
def __init__(self, name, age, teach):
# super()函数用于调用父类中的方法
super().__init__(name, age)
self.teach = teach
# 类方法重写
def myInfo(self):
infomation = (f'我的名字:{self.name},我的年龄:{self.age},我的教学内容:{self.teach}')
return infomation
teacher1 = Teacher('Python全栈开发-基础入门', 35, 'Python')
# 调用了继承于父类的myInfo()方法
print(teacher1.myInfo())
通过上面的程序,可以得知如果在子类中重写了从父类继承来的方法,那么当在类的外部通过子类的对象调用该方法时,Python总是会执行子类中重写的方法。那么,这就产生了一个新的问题,即如何调用父类中被重写的这个方法。
其实很简单,因为Python中的类可以看做是一个独立空间,而类的方法其实就是该空间中的一个函数,所以可以直接使用类名来调用其方法,但是需要注意的是,Python不会为该方法的第一个参数self自动赋值,因此采用这种调用方法,需要手动为参数self赋值。示例代码如下:
# 资源包\Code\chapter10\10.4\1024.py
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
def myInfo(self):
infomation = (f'我的名字:{self.name},我的年龄:{self.age}')
return infomation
class Teacher(Person):
def __init__(self, name, age, teach):
# super()函数用于调用父类中的方法
super().__init__(name, age)
self.teach = teach
# 类方法重写
def myInfo(self):
infomation = (f'我的名字:{self.name},我的年龄:{self.age},我的教学内容:{self.teach}')
return infomation
teacher1 = Teacher('Python全栈开发-基础入门', 35, 'Python')
# 调用父类的myInfo()方法
print(Person.myInfo(teacher1))
10.4.3 多继承
大部分面向对象的编程语言,都只支持单继承,即子类有且只能有一个父类。而Python却支持多继承。和单继承相比,多继承容易让代码逻辑复杂、思路混乱,一直备受争议,所以建议读者在中小型的项目中尽量减少使用。
使用多继承经常需要面临的问题是,多个父类中包含同名的方法,即命名冲突。对于这种情况,Python的处置措施是:首先查找当前子类中的同名属性或方法;如果子类中没有查找到,则根据子类继承多个父类时这些父类的前后次序进行查找,即排在前面父类中的同名属性或方法会覆盖排在后面父类中的同名属性或方法;如果父类中仍然没有该同名属性或方法,则再到父类的父类中进行查找,以此类推。示例代码如下:
# 资源包\Code\chapter10\10.4\1025.py
class ParentClass1(object):
def __init__(self):
self.val = 'pc1'
def run(self):
print('ParentClass1')
class ParentClass2(object):
def __init__(self):
self.val = 'pc2'
def run(self):
print('ParentClass2')
class SubClass1(ParentClass1, ParentClass2):
pass
class SubClass2(ParentClass2, ParentClass1):
pass
class SubClass3(ParentClass1, ParentClass2):
def __init__(self):
self.val = 'sc3'
def run(self):
print('SubClass3')
sub1 = SubClass1()
sub1.run()
print(sub1.val)
sub2 = SubClass2()
sub2.run()
print(sub2.val)
sub3 = SubClass3()
sub3.run()
print(sub3.val)
需要强调一点,虽然Python在语法上支持多继承,但是强烈建议读者不要使用多继承。
10.4.4 super()函数
通过上一小节的学习,得知子类继承的多个父类中包含同名的方法,则子类的对象在调用该方法时,会优先选择排在最前面的父类中的方法,很显然,这个方法也包括构造方法。示例代码如下:
# 资源包\Code\chapter10\10.4\1026.py
class Audi(object):
def __init__(self, audi_tech):
self.audi_tech = audi_tech
def audiCarTech(self):
return (f' Aub的技术由{self.audi_tech}提供')
class BMW(object):
def __init__(self, bmw_oil):
self.bmw_oil = bmw_oil
def BMWCarOil(self):
return (f' Aub的机油由{self.bmw_oil}提供')
# 由奥迪和宝马联合共创的汽车品牌Aub
class Aub(Audi, BMW):
pass
aub = Aub('奥迪')
print(aub.audiCarTech())
# 报错,由于只继承了Audi的构造方法,所以提示没有属性bmw_oil
print(aub.BMWCarOil())
上面程序中,Aub类同时继承Audi类和BMW类,但是Audi类在前,这就意味着,在创建Aub类的对象时,其会调用从Audi类继承来的构造方法,而BMW类中的构造方法会被"舍弃",即未得到执行,进而导致BMW类中的实例属性bmw_oil未被创建,所以print(aub.BMWCarOil())报错,即Aub类没有实例属性bmw_oil。
针对这种情况,正确的处理方式是定义Aub类自己的构造方法,即重写父类的构造方法,但需要注意,如果在子类中定义构造方法,则必须在子类构造方法中调用父类的构造方法。
在子类的构造方法中调用父类构造方法有2种方式,分别是:一是使用未绑定方法,即使用"类名.构造方法"的形式,需要注意的是,Python不会为构造方法的第一个参数self自动赋值,因此采用这种调用方法,需要手动为参数self赋值;二是使用super()函数,super()函数用于调用父类中的方法,如果在使用单继承的时候直接用类名调用父类的方法是没有问题的,但是如果涉及到多继承,则super()函数只能调用多个父类中排在第一位的父类中的构造方法。示例代码如下:
# 资源包\Code\chapter10\10.4\1027.py
class Audi(object):
def __init__(self, audi_tech):
self.audi_tech = audi_tech
def audiCarTech(self):
return (f'Aub的技术由{self.audi_tech}提供')
class BMW(object):
def __init__(self, bmw_oil):
self.bmw_oil = bmw_oil
def BMWCarOil(self):
return (f'Aub的机油由{self.bmw_oil}提供')
# 由奥迪和宝马联合共创的汽车品牌Aub
class Aub(Audi, BMW):
def __init__(self, audi_tech, bmw_oil):
super().__init__(audi_tech)
BMW.__init__(self, bmw_oil)
aub = Aub('奥迪', '宝马')
print(aub.audiCarTech())
print(aub.BMWCarOil())
综上所述,涉及到多继承时,在子类的构造方法中,调用多个父类中排在第一位的父类中的构造方法的方式有2种,即未绑定方法和super()函数,而调用其它父类构造方法的方式只能使用未绑定方法。
当然了,super()函数不仅能调用父类中的构造方法,该函数还可以调用父类中的任意类方法。示例代码如下:
# 资源包\Code\chapter10\10.4\1028.py
class Audi(object):
def __init__(self, audi_type):
self.audi_type = audi_type
def audiCarInfo(self):
return (f'这是辆奥迪{self.audi_type}')
class Audi_A8(Audi):
def __init__(self, audi_type, audi_size):
super().__init__(audi_type)
self.audi_size = audi_size
def audiCarInfo(self):
# super()函数调用父类中的类方法-实例方法
info = super().audiCarInfo()
return (f'{info},是辆{self.audi_size}的奥迪轿车')
audi = Audi_A8('A8', '豪华型')
print(audi.audiCarInfo())