python面向对象(继承)
一 继承
1、什么是继承
1)继承是一种创建新类的方式,新建的类可称为子类或派生类,父类又可称为基类或超类
子类会遗传父类的属性
2)需要注意的是:python支持多继承
在python中,新建的类可以继承一个或多个父类
class Parent1: pass class Parent2: pass class Sub1(Parent1): # 单继承 pass class Sub2(Parent1, Parent2): # 多继承 pass print(Sub1.__bases__) print(Sub2.__bases__) ''' __bases__用来查看继承哪些父类 (<class '__main__.Parent1'>,) (<class '__main__.Parent1'>, <class '__main__.Parent2'>) ''' # PS:在python2中有经典类与新式类之分 # 新式类:继承了object类的子类,以及该子类的子类 # 经典:没有继承object类的子类,以及该子类的子类子子类 # PS2:在python3中没有继承任何类,那么会默认继承object类,所以python3中所有的类都是新式类 # print(Parent1.__bases__) # print(Parent2.__bases__)
3)python的多继承
优点:子类可以同时遗传多个父类的属性,最大限度地重用代码
缺点:
1、违背人的思维习惯:继承表达的是一种什么“是”什么的关系
2、代码可读性会变差
3、不建议使用多继承,有可能会引发可恶的菱形问题,扩展性变差。
如果真的涉及到一个子类不可避免地要重用多个父类的属性,应该使用Mixins
2、为何要用继承:用来解决类与类之间代码冗余问题
3、如何实现继承
# 示范1:类与类之间存在冗余问题 class Student: school = 'oldboy' def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex def choose_course(self): print('{}正在选课'.format(self.name)) class Teather: school = 'oldboy' def __init__(self, name, age, sex, salary, level): self.name = name self.age = age self.sex = sex self.salary = salary self.level = level def score(self): print('老师{}正在给学生打分'.format(self.name))
# 示范2:基于继承解决类与类之间的冗余问题 class OldboyPeople: school = 'oldboy' def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex class Student(OldboyPeople): def choose_course(self): print('{}正在选课'.format(self.name)) # stu_obj1 = Student('lq', 18, 'male') # print(stu_obj1.__dict__) # print(stu_obj1.school) # stu_obj1.choose_course() class Teacher(OldboyPeople): school = 'oldboy' def __init__(self, name, age, sex, salary, level): # 老师的空对象,'ZD', 32, 'female', 4000, 10 OldboyPeople.__init__(self, name, age, sex) self.salary = salary self.level = level def score(self): print('老师{}正在给学生打分'.format(self.name)) tea_obj = Teacher('ZD', 32, 'female', 4000, 10) print(tea_obj.__dict__)
二 单继承下的属性查找
# 单继承背景下的属性查找 # 示范一 class Foo: def f1(self): print('Foo.f1') def f2(self): print('FOO.f2') self.f1() class Bar(Foo): def f1(self): print('Bar.f1') obj1 = Bar() obj1.f2() ''' 对象的属性查找,先找自己对象的名称空间,在去类的名称空间找,再去父类名称空间找 FOO.f2 Bar.f1 '''
# 示范二(要调用Foo中的f1,可以用Foo类直接调f1): class Foo: def f1(self): print('Foo.f1') def f2(self): print('FOO.f2') Foo.f1(self) # 调用当前类中的f1 class Bar(Foo): def f1(self): print('Bar.f1') obj2 = Bar() obj2.f2() ''' FOO.f2 Foo.f1 '''
# 示范三(隐藏属性的方法查找): class Foo: def __f1(self): print('Foo.f1') def f2(self): print('FOO.f2') self.__f1() # 调用当前类中的f1 class Bar(Foo): def f1(self): print('Bar.f1') obj2 = Bar() obj2.f2() ''' FOO.f2 Foo.f1 '''
三 继承的实现原理
1、菱形问题介绍与mro
mro是c3算法实现的,python3可以调用.mro(),python2无法调用
class A(object): def test(self): print('from A') class B(A): def test(self): print('from B') class C(A): def test(self): print('from C') class D(B, C): pass print(D.mro()) # 类D以及类D的对象访问属性都是参照该类的mro列表 # [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>] obj = D() obj.test() # 结果为:from B print(C.mro()) # 类C以及类C的对象访问属性都是参照该类的mro列表 # [<class '__main__.C'>, <class '__main__.A'>, <class 'object'>] # 总结:类相关的属性查找(类名.属性,该类的对象.属性),都是参照该类的mro列表
2、深度优化和广度优化
2.1 如果多继承是非菱形继承,经典类与新式类的属性查找顺序一样:
都是一个分支一个分支地找下去,最后找object
class E: def test(self): print('from E') class F: def test(self): print('from F') class B(E): def test(self): print('from B') class C(F): def test(self): print('from C') class D: def test(self): print('from D') class A(B, C, D): def test(self): print('from A') print(A.mro()) ''' [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.F'>, <class '__main__.D'>, <class 'object'>] ''' obj = A() obj.test() # 结果为:from B # 可依次注释上述类中的方法test来进行验证
2.2 如果多继承是菱形继承,经典类与新式类的属性查找顺序不一样:
# 经典类:深度优先,会在检索第一条分支的时候就直接一条道走到黑,既会检索大脑袋(共同的父类G)
# 新式类:广度优先,会检索最后一条分支的时候检索大脑袋(G)
class G: pass class E(G): def test(self): print('from E') class F(G): def test(self): print('from F') class B(E): def test(self): print('from B') class C(F): def test(self): print('from C') class D(G): def test(self): print('from D') class A(B, C, D): def test(self): print('from A') print(A.mro()) ''' [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.F'>, <class '__main__.D'>, <class '__main__.G'>, <class 'object'>]''' obj = A() obj.test() # 结果为:from B # 可依次注释上述类中的方法test来进行验证 # 经典类:也就是在python2中没有继承object,查找顺序是A-->B-->E-->G-->C-->F-->D # 新式类:也就是python3和python2中继承了object,查找顺序是A-->B-->E-->C-->F-->D-->G # 总结: # 多继承到底要不要用??? # 要用,但是规避几点问题 # 1、继承结构尽量不要过去复杂 # 2、要在多继承的背景下满足继承的什么“是”什么的关系---》mixins
3、子类派生的新方法如何重用父类的功能
# 方式一:指名道姓调用某一个类下的函数---》不依赖于继承关系 class OldboyPeople: def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex def f1(self): print('{} say hello'.format(self.name)) class Teacher(OldboyPeople): def __init__(self, name, age, sex, level, salary): OldboyPeople.__init__(self, name, age, sex) # 调用的是函数,因而需要传入self self.level = level self.salary = salary tea_obj = Teacher('lq', 34, 'male', 10, 3000) print(tea_obj.__dict__) # {'name': 'lq', 'age': 34, 'sex': 'male', 'level': 10, 'salary': 3000} # 方式二:super()调用父类提供给自己的方法---》严格依赖继承关系 # 调用super()会得到一个特殊的对象,该对象会参照发起属性查找的那个类的mro,去当前类的父类(mro中的父类)中找属性 class OldboyPeople: def __init__(self, name, age, sex): self.name = name self.age = age self.sex = sex def f1(self): print('{} say hello'.format(self.name)) class Teacher(OldboyPeople): def __init__(self, name, age, sex, level, salary): # super(Teacher,self).__init__(name,age,sex) # python2中super()的使用格式 super().__init__(name, age, sex) # 调用的是方法,自动传入对象 self.level = level self.salary = salary print(Teacher.mro()) # [<class '__main__.Teacher'>, <class '__main__.OldboyPeople'>, <class 'object'>] tea_obj = Teacher('lq', 34, 'male', 10, 3000) print(tea_obj.__dict__) # super()案例 # super()生成特殊对象,按照C类生成的mro顺序查找属性,B在A之后,需要去B中查找属性,结果为'from B' class A: def test(self): super().test() class B: def test(self): print('from B') class C(A, B): pass print(C.mro()) obj = C() obj.test() # from B
四 组合
''' 在一个类中以另外一个类的对象作为数据属性,称为类的组合。 组合与继承都是用来解决代码的重用性问题。不同的是:继承 是一种“是”的关系,比如老师是人、学生是人,当类之间有很 多相同的之处,应该使用继承;而组合则是一种“有”的关系, 比如老师有生日,老师有多门课程,当类之间有显著不同, 并且较小的类是较大的类所需要的组件时,应该使用组合,如下示例 ''' class Course: def __init__(self, name, period, price): self.name = name self.period = period self.price = price def tell_info(self): print(f'<{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) # 老师有生日,Date类的对象birth,作为了Teacher类的数据属性(组合) self.courses = [] # 老师有课程,可以在实例化后,往该列表中添加Course类的对象,Course类的对象作为Teacher类的数据属性(组合) 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() # <1990-3-23> # 重用Course类的功能 for obj in teacher1.courses: obj.tell_info() ''' <python 3mons 3000.0> <linux 5mons 5000.0> '''