类的继承
一、继承的介绍
1.1、继承的应用场景与作用
1.1.1、应用场景
之前的例子中,我们定义人这个类。实际上,我们都有职业角色,比如程序员、老师、医生、警察等。现在我们来定义一个程序员的类。这时候会发现,我们很多属性,其实在人这个类中已经定义了。比如姓名,年龄。如果在程序员这个类中重新定义一遍,很麻烦。故为解决代码重用问题,我们在程序开发的过程中引入继承。
1.1.2、作用
继承的功能之一就是用来解决代码重用问题
这样用已经有的类建立一个新的类,这样就重用了已经有的软件中的一部分设置大部分,大大节省了编程工作量,这就是常说的软件重用,不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,这样就是大大缩短了软件开发周期,对大型软件开发来说,意义重大.
1.2、继承的相关定义
1.2.1、继承的定义
在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时我们不可能从头开始写一个类B,想让B‘遗传’A的所有属性(数据属性和函数属性),实现代码重用。这个概念就叫做继承。
1.2.2、继承的派生类(子类)
上述所说,B遗传A的所属属性,其中B类就是派生类(子类)
1.2.3、继承的基类(父类)
上述所说,B遗传A的所属属性,其中A类就是基类(父类)
1.3、继承的分类
1.3.1、根据继承基类个数分类的单继承与多继承
在python中,新建的类可以继承一个或多个父类,父类又可以成为基类或超类,新建的类称为派生类或子类。可以分为单继承和多继承
a、单继承
语法
class DerivedClassName(BaseClassName1):
<statement-1>
.
<statement-N>
b、多继承
语法
class DerivedClassName(BaseClassName1, BaseClassName2, BaseClassName3): # python支持多继承,用逗号分隔开多个继承的类
<statement-1>
.
<statement-N>
说明
需要注意圆括号中父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索 即方法在子类中未找到时,从左到右查找父类中是否包含方法。
1.3.2、根据继承基类有无的经典类与新式类
- 只有在python2中才分新式类和经典类,python3中统一都是新式类
- 在python2中,没有显式的继承object类的类,以及该类的子类,都是经典类
- 在python2中,显式地声明继承object的类,以及该类的子类,都是新式类
- 在python3中,无论是否继承object,都默认继承object,即python3中所有类均为新式类
1.4、继承的关系确认
1.4.1、如何通过抽象方法来确定类之间的继承关系
抽象最主要的作用 是划分类别(可以隔离关注点,降低复杂度)
抽象即抽取类似或者说比较像的部分。抽象分成两个层次:
- 1.将奥巴马和梅西这俩对象比较像的部分抽取成类;
- 2.将人,猪,狗这三个类比较像的部分抽取成父类。
继承:是基于抽象的结果,通过编程语言去实现它,肯定是先经历抽象这个过程,才能通过继承的方式去表达出抽象的结构
抽象只是分析和设计的过程中,一个动作或者说一种技巧,通过抽象可以得到类
1.4.2、如何查看继承关系
通过查看类属性__bases__
可以确认
代码示例
>>> SubClass1.__bases__ #__base__只查看从左到右继承的第一个子类,__bases__则是查看所有继承的父类
(<class '__main__.ParentClass1'>,)
>>> SubClass2.__bases__
(<class '__main__.ParentClass1'>, <class '__main__.ParentClass2'>)
说明
注意:如果没有指定基类,python的类会默认继承object类,object是所有python类的基类,它提供了一些常见方法(如__str__)的实现。
>>> ParentClass1.__bases__
(<class 'object'>,)
>>> ParentClass2.__bases__
(<class 'object'>,)
二、继承的使用
2.1、继承后使用父类的方法与属性
2.1.1、使用继承过来的属性(类的实例变量、类变量。类方法)
由于父类继承了子类,所以对父类来说,也有了对应的 类的实例变量、类变量、类方法。
代码示例
class Person:
# 类变量
species = '人类' # 物种:人类
def __init__(self, name, age):
# 类的实例变量
self.name = name
self.age = age
# 类方法
def speak(self):
print("%s 说: 我 %d 岁。" % (self.name, self.age))
class Programmer(Person):
pass
def programming(self):
print("我 %s ,会编程" % self.name)
if __name__ == '__main__':
p1 = Person('张三', 18)
p1.speak()
p2 = Programmer('李四', 19)
print(p2.name, p2.species)
p2.speak()
输出结果
张三 说: 我 18 岁。
李四 人类
李四 说: 我 19 岁。
说明
- 变量使用是通过类实例对象+.方法,比如
p2.name
和p2.species
- 类方法的使用是通过类实例对象+.方法(),像运行函数一样()运行,比如
p2.speak()
2.1.2、继承后修改与额外添加属性(类的实例变量、类变量。类方法)
当然继承过来的属性不满足我们的需要,我们需要给新的类添加额外的属性
存在如下的情况:
一、继承过来的某些属性,不满足我们需求,需要修改
- 修改继承过来的变量
- 修改继承过来的类的实例变量
- 修改继承过来的类方法
二、继承过来的某些属性,不满足我们需求,需要额外添加
- 添加额外的类变量
- 添加额外类的实例变量(指明道姓 和 super方法)
- 添加新的类方法
代码示例1
class Person:
# 类的变量
species = 'human' # 物种:人类
def __init__(self, name, age):
# 类的实例变量
self.name = name
self.age = age
# 类方法
def speak(self):
print("%s 说: 我 %d 岁。" % (self.name, self.age))
class Programmer(Person):
# 类的变量(额外添加)
occupation = '程序员'
# 类的变量(修改)
species = '人类'
# 重写类的构造方法
def __init__(self, lastname, firstname, work_type, age):
# 类的实例变量
self.firstname = firstname
self.lastname = lastname
self.work_type = work_type
self.age = age
self.working_age = 0
# 重写类的方法
def speak(self):
print('我 %s%s ,会说话' % (self.lastname, self.firstname))
# 定义自己的类方法
def programming(self):
print("我 %s%s ,会编程" % (self.lastname, self.firstname))
if __name__ == '__main__':
p1 = Person('张三', 18)
print(p1.name, p1.species)
p1.speak()
p2 = Programmer('李', '四', 'Python开发工程师', 19)
print(p2.lastname, p2.firstname, p2.species, p2.occupation)
p2.speak()
p2.programming()
输出结果
张三 human
张三 说: 我 18 岁。
李 四 人类 程序员
我 李四 ,会说话
我 李四 ,会编程
说明1
- 修改了继承过来的类实例变量,我们发现类person中定义的类实例变量不好用,于是我们将name重新定义成firstname,lastname(名,姓)于是重写了类的构造方法
def __init__(self, lastname, firstname, work_type, age):
并添加了额外的类实例变量 work_type。 - 修改了继承过来的类变量
species = 'human'
,将它的值重human 改成了 人类。 - 修改了继承过来的类方法
def speak(self):
使它适用新的类实例变量 lastname 和 firstname。 - 额外添加了类变量
occupation = '程序员'
- 额外添加了类方法
programming(self)
但有时候,我们不需要全部修改,希望在父类的基础上使用其属下
代码示例2
class Person:
# 类的变量
species = 'human' # 物种:人类
def __init__(self, lastname, firstname, age):
# 类的实例变量
self.firstname = firstname
self.lastname = lastname
self.age = age
# 类方法
def speak(self):
print('我 %s%s ,会说话' % (self.lastname, self.firstname))
class Programmer(Person):
# 类的变量(额外添加)
occupation = '程序员'
# 类的变量(修改)
species = '人类'
# 重写类的构造方法
def __init__(self, lastname, firstname, work_type, age):
# 类的实例变量
Person.__init__(self, lastname, firstname, age)
self.work_type = work_type
self.working_age = 0
# 重写类的方法
def speak(self):
Person.speak(self)
print('还会写程序说hello')
# 定义自己的类方法
def programming(self):
print("我 %s%s ,会编程" % (self.lastname, self.firstname))
if __name__ == '__main__':
p1 = Person('张', '三', 18)
print(p1.lastname, p1.firstname, p1.species)
p1.speak()
p2 = Programmer('李', '四', 'Python开发工程师', 19)
print(p2.lastname, p2.firstname, p2.species, p2.occupation)
p2.speak()
p2.programming()
输出结果
张 三 human
我 张三 ,会说话
李 四 人类 程序员
我 李四 ,会说话
还会写程序说hello
我 李四 ,会编程
说明2
通过指名道姓,即父类名.父类方法(), Person.__init__(self, lastname, firstname, age)
和 Person.speak(self)
,我们可以在父类的基础上额外添加属性,而不需要想上面代码一样,大量的修改。注意,指名道姓的方法,需要传递的第一个参数是self
代码示例3
class Programmer(Person):
# 类的变量(额外添加)
occupation = '程序员'
# 类的变量(修改)
species = '人类'
# 重写类的构造方法
def __init__(self, lastname, firstname, work_type, age):
# 类的实例变量
# Person.__init__(self, lastname, firstname, age)
super().__init__(lastname, firstname, age)
self.work_type = work_type
self.working_age = 0
# 重写类的方法
def speak(self):
#Person.speak(self)
super().speak()
print('还会写程序说hello')
# 定义自己的类方法
def programming(self):
print("我 %s%s ,会编程" % (self.lastname, self.firstname))
if __name__ == '__main__':
p1 = Person('张', '三', 18)
print(p1.lastname, p1.firstname, p1.species)
p1.speak()
p2 = Programmer('李', '四', 'Python开发工程师', 19)
print(p2.lastname, p2.firstname, p2.species, p2.occupation)
p2.speak()
p2.programming()
说明3
使用super()方法,达到相同效果—推荐使用, super().__init__(lastname, firstname, age)
和 super().speak()
,
2.3、继承后属性的查找顺序与规则
2.3.1、背景介绍
python到底是如何实现继承的,对于你定义的每一个类,python会计算出一个方法解析顺序(MRO)列表,这个MRO列表就是一个简单的所有基类的线性顺序列表,例如
>>> F.mro() #等同于F.__mro__
[<class '__main__.F'>, <class '__main__.D'>, <class '__main__.B'>,
<class '__main__.E'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]
为了实现继承,python会在MRO列表上从左到右开始查找基类, 直到找到第一个匹配这个属性的类为止。而这个MRO列表的构造是通过一个C3线性化算法来实现的。我们不去深究这个算法的数学原理,它实际上就是合并所有父类的MRO列表并遵循如下三条准则:
- 子类会先于父类被检查
- 多个父类会根据它们在列表中的顺序被检查
- 如果对下一个类存在两个合法的选择, 选择第一个父类
在Java和C#中子类只能继承一个父类,而Python中子类可以同时继承多个父类,如果继承了多个父类,在Python3中,会按照深度优先的原则访问,但object基类特殊。(注:Python3中所有的类,默认是继承object这个类的)
2.3.2、举例说明
a、关系图
我们假定类的关系如下:
b、相关代码
class A(object):
pass
class B(object):
pass
class C(A):
pass
class D(object):
pass
class E(B,C):
pass
class F(D):
pass
class G:
pass
class H(E,F,G):
pass
print(H.mro())
运行结果
[<class '__main__.H'>, <class '__main__.E'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class '__main__.F'>, <class '__main__.D'>, <class '__main__.G'>, <class 'object'>]
c、说明
上述示例的结果,访问顺序是 H-E-B-C-A-F-D-G-object ,这样证实了是安装深度优先的规则访问的。如果改名多继的类顺序,访问的顺序也会发生变化。可以下去自己尝试一下。
这样你就知道继承下的实例,访问类中成员属性的顺序了。