文章目录
python继承
概述
面向对象编程 (OOP) 语言的一个主要功能就是“继承”。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
通过继承创建的新类称为“子类”或“派生类”,被继承的类称为“基类”、“父类”或“超类”,继承的过程,就是从一般到特殊的过程。在某些 OOP 语言中,一个子类可以继承多个基类。但是一般情况下,一个子类只能有一个基类,要实现多重继承,可以通过多级继承来实现。
继承概念的实现方式主要有2类:实现继承、接口继承。
- 实现继承是指使用基类的属性和方法而无需额外编码的能力。
- 接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力(子类重构爹类方法)。
在考虑使用继承时,有一点需要注意,那就是两个类之间的关系应该是“属于”关系。例如,Employee 是一个人,Manager 也是一个人,因此这两个类都可以继承 Person 类。但是 Leg 类却不能继承 Person 类,因为腿并不是一个人。
OO开发范式大致为:划分对象→抽象类→将类组织成为层次化结构(继承和合成) →用类与实例进行设计和实现几个阶段。
类的继承
继承的定义
class Animal(object):
def run(self):
print('Animal is running...')
class Dog(Animal):
pass
class Cat(Animal):
pass
dog = Dog()
dog.run()
cat = Cat()
cat.run()
构造函数的继承
如果我们要给实例 c 传参,我们就要使用到构造函数,那么构造函数该如何继承,同时子类中又如何定义自己的属性?
继承类的构造方法:
1.经典类的写法: 父类名称.init(self,参数1,参数2,…)
\2. 新式类的写法:super(子类,self).init(参数1,参数2,…)
class Person(object):
def __init__(self, name, age):
self.name = name
self.age = age
self.weight = 'weight'
def talk(self):
print("person is talking....")
class Chinese(Person):
def __init__(self, name, age, language): # 先继承,在重构
Person.__init__(self, name, age) # 继承父类的构造方法,也可以写成:super(Chinese,self).__init__(name,age)
self.language = language # 定义类的本身属性
def walk(self):
print('is walking...')
class American(Person):
pass
c = Chinese('bigberg', 22, 'Chinese')
如果我们只是简单的在子类Chinese中定义一个构造函数,其实就是在重构。这样子类就不能继承父类的属性了。所以我们在定义子类的构造函数时,要先继承再构造,这样我们也能获取父类的属性了。
子类构造函数基础父类构造函数过程如下:
实例化对象c ----> c 调用子类__init__() ---- > 子类__init__()继承父类__init__() ----- > 调用父类 init()
子类对父类方法的重写
如果我们对基类/父类的方法需要修改,可以在子类中重构该方法。如下的talk()方法
class Animal(object):
def run(self):
print('Animal is running...')
class Dog(Animal):
def run(self):
print('hello')
class Cat(Animal):
pass
dog = Dog()
dog.run()
cat = Cat()
cat.run()
类继承的事例
class SchoolMember(object):
'''学习成员基类'''
member = 0
def __init__(self, name, age, sex):
self.name = name
self.age = age
self.sex = sex
self.enroll()
def enroll(self):
'注册'
print('just enrolled a new school member [%s].' % self.name)
SchoolMember.member += 1
def tell(self):
print('----%s----' % self.name)
for k, v in self.__dict__.items():
print(k, v)
print('----end-----')
def __del__(self):
print('开除了[%s]' % self.name)
SchoolMember.member -= 1
class Teacher(SchoolMember):
'教师'
def __init__(self, name, age, sex, salary, course):
SchoolMember.__init__(self, name, age, sex)
self.salary = salary
self.course = course
def teaching(self):
print('Teacher [%s] is teaching [%s]' % (self.name, self.course))
class Student(SchoolMember):
'学生'
def __init__(self, name, age, sex, course, tuition):
SchoolMember.__init__(self, name, age, sex)
self.course = course
self.tuition = tuition
self.amount = 0
def pay_tuition(self, amount):
print('student [%s] has just paied [%s]' % (self.name, amount))
self.amount += amount
t1 = Teacher('Wusir', 28, 'M', 3000, 'python')
t1.tell()
s1 = Student('haitao', 38, 'M', 'python', 30000)
s1.tell()
s2 = Student('lichuang', 12, 'M', 'python', 11000)
print(SchoolMember.member)
del s2
print(SchoolMember.member)
# 输出
----end-----
just enrolled a new school member [haitao].
----haitao----
age 38
sex M
name haitao
amount 0
course python
tuition 30000
----end-----
just enrolled a new school member [lichuang].
3
开除了[lichuang]
2
开除了[Wusir]
开除了[haitao]
Python 类的多态
在说明多态是什么之前,我们在 Child 类中重写 print_title() 方法:若为male,print boy;若为female,print girl
class Person(object):
def __init__(self,name,sex):
self.name = name
self.sex = sex
def print_title(self):
if self.sex == "male":
print("man")
elif self.sex == "female":
print("woman")
class Child(Person): # Child 继承 Person
def print_title(self):
if self.sex == "male":
print("boy")
elif self.sex == "female":
print("girl")
May = Child("May","female")
Peter = Person("Peter","male")
print(May.name,May.sex,Peter.name,Peter.sex)
May.print_title()
Peter.print_title()
当子类和父类都存在相同的 print_title()方法时,子类的 print_title() 覆盖了父类的 print_title(),在代码运行时,会调用子类的 print_title()
这样,我们就获得了继承的另一个好处:多态。
多态的好处就是,当我们需要传入更多的子类,例如新增 Teenagers、Grownups 等时,我们只需要继承 Person 类型就可以了,而print_title()方法既可以直不重写(即使用Person的),也可以重写一个特有的。这就是多态的意思。调用方只管调用,不管细节,而当我们新增一种Person的子类时,只要确保新方法编写正确,而不用管原来的代码。这就是著名的“开闭”原则:
- 对扩展开放(Open for extension):允许子类重写方法函数
- 对修改封闭(Closed for modification):不重写,直接继承父类方法函数
子类重写构造函数
子类可以没有构造函数,表示同父类构造一致;子类也可重写构造函数;现在,我们需要在子类 Child 中新增两个属性变量:mother 和 father,我们可以构造如下(建议子类调用父类的构造方法,参见后续代码):
class Person(object):
def __init__(self,name,sex):
self.name = name
self.sex = sex
class Child(Person): # Child 继承 Person
def __init__(self,name,sex,mother,father):
self.name = name
self.sex = sex
self.mother = mother
self.father = father
May = Child("May","female","April","June")
print(May.name,May.sex,May.mother,May.father)
Person
若父类构造函数包含很多属性,子类仅需新增1、2个,会有不少冗余的代码,这边,子类可对父类的构造方法进行调用,参考如下:
class Person(object):
def __init__(self,name,sex):
self.name = name
self.sex = sex
class Child(Person): # Child 继承 Person
def __init__(self,name,sex,mother,father):
Person.__init__(self,name,sex) # 子类对父类的构造方法的调用
self.mother = mother
self.father = father
May = Child("May","female","April","June")
print(May.name,May.sex,May.mother,May.father)
python多重继承
Python虽然支持多继承,但是python支持的多继承也是有限的。
问题的提出
如果不同的父类中存在 同名的方法,子类对象在调用方法时,会调用哪一个父类中的方法呢?
Python 中的 MRO —— 方法搜索顺序
-
Python 中针对 类 提供了一个内置属性 mro 可以查看方法搜索顺序
-
MRO 是 method resolution order,主要用于在多继承时判断 方法、属性 的调用 路径
print(C.__mro__) #C是多继承后的类名
输出结果
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
- 在搜索方法时,是按照 mro 的输出结果 从左至右 的顺序查找的
- 如果在当前类中 找到方法,就直接执行,不再搜索
- 如果 没有找到,就查找下一个类 中是否有对应的方法,如果找到,就直接执行,不再搜索
- 如果找到最后一个类,还没有找到方法,程序报错
多继承的使用
#1.多继承:子类有多个父类
class Human:
def __init__(self, sex):
self.sex = sex
def p(self):
print("这是Human的方法")
class Person:
def __init__(self, name):
self.name = name
def p(self):
print("这是Person的方法")
def person(self):
print("这是我person特有的方法")
class Teacher(Person):
def __init__(self, name, age):
super().__init__(name)
self.age = age
class Student(Human, Person):
def __init__(self, name, sex, grade):
#要想调用特定父类的构造器可以使用父类名.__init__方式。多继承使用super,会有一个坑,具体参考后面
Human.__init__(self,sex)
Person.__init__(self,name)
self.grade = grade
class Son(Human, Teacher):
def __init__(self, sex, name, age, fan):
Human.__init__(self, sex)
Teacher.__init__(self, name, age)
self.fan = fan
# ------创建对象 -------------
stu = Student("tom", "male", 88)
print(stu.name,stu.sex,stu.grade)
stu.p() # 虽然父类Human和Person都有同名P()方法 ,但是调用的是括号里的第一个父类Human的方法
son1 = Son("jerry", "female", 18, "打球")
son1.person() # 可以调用父类的父类的方法。
son1.p() # 子类调用众多父类中同名的方法,按继承的顺序查找。
=====================================================================================
tom male 88
这是Human的方法
这是我person特有的方法
这是Human的方法
总结
-
需要注意圆括号中继承父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索 即方法在子类中未找到时,从左到右查找父类中是否包含方法。
-
支持多层父类继承,子类会继承父类所有的属性和方法,包括父类的父类的所有属性 和 方法。
多继承的使用注意事项
#1.多继承子类对父类构造方法的调用
class Human:
def __init__(self,sex):
self.sex = sex
def p(self):
print("这是Human的方法")
def str1(self):
print("this si"+str(self.sex))
class Person:
def __init__(self,name):
self.name = name
def p(self):
print("这是Person的方法")
def person(self):
print("这是我person特有的方法")
def str2(self):
print( "this is:" + str(self.name))
class Student(Human,Person): #注意子类如果没有构造方法时,按括号内父类的继承顺序去继承父类构造方法,只继承一个
def prin(self):
print("student")
#------创建对象 -------------
#stu1=Studnent("男","tom")报错。
stu = Student("sex") #这里继承的是Huma的构造方法。
stu.p()
stu.str1()
#stu.str2()报错,因为即使human和person都是一个参数的构造方法,但是这里继承调用的是第一个Human的构造方法
====================================================================================
这是Human的方法
this sisex
总结:
- 子类从多个父类派生,而子类又没有自己的构造函数时,
- 按顺序继承,哪个父类在最前面且它又有自己的构造函数,就继承它的构造函数;
- 如果最前面第一个父类没有构造函数,则继承第2个的构造函数,第2个没有的话,再往后找,以此类推。
多继承时使用super调用父类属性方法的注意事项
不使用super调用父类方法,使用父类名.方法名的形式。
class Parent(object):
def __init__(self, name):
print('parent的init开始被调用')
self.name = name
print('parent的init结束被调用')
class Son1(Parent):
def __init__(self, name, age):
print('Son1的init开始被调用')
self.age = age
Parent.__init__(self, name) #直接使用父类名.方法名的方式调用父类的__init__方法
print('Son1的init结束被调用')
class Son2(Parent):
def __init__(self, name, gender):
print('Son2的init开始被调用')
self.gender = gender
Parent.__init__(self, name) #
print('Son2的init结束被调用')
class Grandson(Son1, Son2):
def __init__(self, name, age, gender):
print('Grandson的init开始被调用')
Son1.__init__(self, name, age) # 单独调用父类的初始化方法
Son2.__init__(self, name, gender)
print('Grandson的init结束被调用')
gs = Grandson('grandson', 12, '男')
print('姓名:', gs.name)
print('年龄:', gs.age)
print('性别:', gs.gender)
'''执行结果如下:
Grandson的init开始被调用
Son1的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son1的init结束被调用
Son2的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son2的init结束被调用
Grandson的init结束被调用
姓名: grandson
年龄: 12
性别: 男
'''
注意:上面代码里当在子类中通过父类名调用时,parent被执行了2次
使用super调用父类中的方法,注意分析程序的执行顺序。
class Parent(object):
def __init__(self, name, *args, **kwargs): # 为避免多继承报错,使用不定长参数,接受参数
print('parent的init开始被调用')
self.name = name
print('parent的init结束被调用')
class Son1(Parent):
def __init__(self, name, age, *args, **kwargs): # 为避免多继承报错,使用不定长参数,接受参数
print('Son1的init开始被调用')
self.age = age
super().__init__(name, *args, **kwargs) # 为避免多继承报错,使用不定长参数,接受参数
print('Son1的init结束被调用')
class Son2(Parent):
def __init__(self, name, gender, *args, **kwargs): # 为避免多继承报错,使用不定长参数,接受参数
print('Son2的init开始被调用')
self.gender = gender
super().__init__(name, *args, **kwargs) # 为避免多继承报错,使用不定长参数,接受参数
print('Son2的init结束被调用')
class Grandson(Son1, Son2):
def __init__(self, name, age, gender):
print('Grandson的init开始被调用')
# 多继承时,相对于使用类名.__init__方法,要把每个父类全部写一遍
# 而super只用一句话,执行了全部父类的方法,这也是为何多继承需要全部传参的一个原因
# super(Grandson, self).__init__(name, age, gender) 效果和下面的一样
super().__init__(name, age, gender)
print('Grandson的init结束被调用')
print(Grandson.__mro__) #搜索顺序
gs = Grandson('grandson', 12, '男')
print('姓名:', gs.name)
print('年龄:', gs.age)
print('性别:', gs.gender)
'''结果如下:
(<class '__main__.Grandson'>, <class '__main__.Son1'>, <class '__main__.Son2'>, <class '__main__.Parent'>, <class 'object'>)
Grandson的init开始被调用
Son1的init开始被调用
Son2的init开始被调用
parent的init开始被调用
parent的init结束被调用
Son2的init结束被调用
Son1的init结束被调用
Grandson的init结束被调用
姓名: grandson
年龄: 12
性别: 男
'''
注意:在上面模块中,当在子类中通过super调用父类方法时,parent被执行了1次。
super调用过程:上面gs初始化时,先执行grandson中init方法, 其中的init有super调用,每执行到一次super时,都会从__mro__方法元组中顺序查找搜索。所以先调用son1的init方法,在son1中又有super调用,这个时候就就根据__mro__表去调用son2的init,然后在son2中又有super调用,这个就根据mro表又去调用parent中的init,直到调用object中的init. 所以上面的打印结果如此,要仔细分析执行过程。
提示:
- super().__init__相对于类名.init,在单继承上用法基本无差
- 但在多继承上有区别,super方法能保证每个父类的方法只会执行一次,而使用类名的方法会导致方法被执行多次,具体看前面的输出结果
- 多继承时,使用super方法,对父类的传参数,应该是由于python中super的算法导致的原因,必须把参数全部传递,否则会报错
- 单继承时,使用super方法,则不能全部传递,只能传父类方法所需的参数,否则会报错
- 多继承时,相对于使用类名.__init__方法,要把每个父类全部写一遍, 而使用super方法,只需写一句话便执行
- 全部父类的方法,这也是为何多继承需要全部传参的一个原因
单继承使用super调用父类方法
class Parent(object):
def __init__(self, name):
print('parent的init开始被调用')
self.name = name
print('parent的init结束被调用')
class Son1(Parent):
def __init__(self, name, age):
print('Son1的init开始被调用')
self.age = age
super().__init__(name) # 单继承不能提供全部参数
print('Son1的init结束被调用')
class Grandson(Son1):
def __init__(self, name, age, gender):
print('Grandson的init开始被调用')
super().__init__(name, age) # 单继承不能提供全部参数
print('Grandson的init结束被调用')
gs = Grandson('grandson', 12, '男')
print('姓名:', gs.name)
print('年龄:', gs.age)
print(‘parent的init开始被调用’)
self.name = name
print(‘parent的init结束被调用’)
class Son1(Parent):
def init(self, name, age):
print(‘Son1的init开始被调用’)
self.age = age
super().init(name) # 单继承不能提供全部参数
print(‘Son1的init结束被调用’)
class Grandson(Son1):
def init(self, name, age, gender):
print(‘Grandson的init开始被调用’)
super().init(name, age) # 单继承不能提供全部参数
print(‘Grandson的init结束被调用’)
gs = Grandson(‘grandson’, 12, ‘男’)
print(‘姓名:’, gs.name)
print(‘年龄:’, gs.age)