第十章 面向对象之继承与派生
继承与派生
继承
当我们创建一个类时,新建的类可以继承一个或多个父类(python支持多继承),父类又可以称为基类或超类,新建的类称为派生类或子类,子类会继承父类的属性,可以减少代码冗余
#单继承和多继承 class ParentClass1: pass class ParentClass2: pass class subClass1(ParentClass1): #单继承 pass class subClass2(ParentClass1,ParentClass2): #多继承,用逗号分隔开多个继承的类 pass #查看继承的父类 print(subClass1.__bases__) print(subClass2.__base__) #只查看从左到右继承的第一个子类 print(subClass2.__bases__) #查看所有继承的父类
#经典类和新式类 print(ParentClass1.__bases__) #object类:所有python类的基类,提供了一些常见方法的实现(如__str__) #经典类:没有继承object的类,以及该类的子类 #新式类:无论是否继承object,都默认继承object的类,以及该类的子类 #在python2中,类分为经典类和新式类;在python3中,所有类均为新式类
继承描述的是子类与父类之间的关系,是一种“什么是什么”(例如:人是动物)的关系。要找出这种关系,必须先抽象再继承。
抽象只是分析和设计过程中,一个动作或者一种技巧,通过抽象可以得到类
class People: def __init__(self,name,age): self.name=name self.age=age class Student(People): def learn(self): print('%s is learning'%self.name) class Teacher(People): def teach(self): print('%s is teaching'%self.name) stu1=Student('lary',18) teacher1=Teacher('egon',20) stu1.learn() teacher1.teach()
属性查找顺序:对象->类->父类->父类的父类。。。
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...') b=Bar() b.f2()
派生
派生:子类可以添加自己的新属性,或者重新定义已经在父类中定义过的属性,一旦定义了与父类重名的属性,那么调用该属性时,以子类自己的属性为准
class People: def __init__(self,name,age): self.name=name self.age=age def tell_info(self): print('name:%s--age:%s'%(self.name,self.age)) class Student(People): def learn(self): print('%s is learning'%self.name) def tell_info(self): print('student:',end='') print('name:%s--age:%s' % (self.name, self.age)) stu1=Student('lary',18) stu1.tell_info()
在子类中,新建的重名的函数属性,在编辑函数内功能的时候,有可能需要重用父类中重名的那个函数功能,应该是用调用普通函数的方式,即:类名.func(),此时就与调用普通函数无异了,因此即使是self参数也要为其传值
class People: def __init__(self,name,age): self.name=name self.age=age class Student(People): def __init__(self,name,age,grade): People.__init__(self,name,age) #调用父类功能 self.grade=grade #新属性
组合
组合:指的是在一个类中以另外一个类的对象作为数据属性
组合与继承:通过继承建立了派生类与基类之间的关系,它是一种“是”的关系,当类之间有很多相同的功能,提取这些共同的功能做成基类;组合的方式建立了类与组合的类之间的关系,它是一种“有”的关系,当类之间有明显的不同,并且较小的类是较大的类所需要的组件时,用组合比较好
class People: def __init__(self,name,age,date_obj): self.name=name self.age=age self.birth=date_obj def tell_info(self): print('name:%s--age:%s'%(self.name,self.age)) class Student(People): def learn(self): print('%s is learning'%self.name) def tell_info(self): print('student:',end='') People.tell_info(self) class Date: def __init__(self,year,mon,date): self.year=year self.mon=mon self.date=date def tell_birth(self): print('birth day is <%s-%s-%s>'%(self.year,self.mon,self.date)) day1=Date(1990,12,12) #day1.tell_birth() stu1=Student('lary',18,day1) stu1.birth.tell_birth()
抽象类:抽象类是一个特殊的类,特殊之处在于只能被继承,不能被实例化。抽象类中只能有抽象方法(没有实现功能),该类不能被实例化,只能被继承,且子类必须实现抽象方法
#抽象类 import abc class Animal(metaclass=abc.ABCMeta): @abc.abstractmethod #定义抽象方法,无需实现功能 def eat(self): pass class People(Animal): #子类继承抽象类,但是必须定义抽象类中定义的方法(如eat方法) def eat(self): print('people is eating') peo1=People() peo1.eat()
继承的实现原理
继承顺序:如果继承关系为非菱形结构,则会按照先找B这一条分支,然后再找C这一条分支,最后找D这一条分支的顺序直到找到我们想要的属性,如果继承关系为菱形结构,那么属性的查找方式有两种,分别是:深度优先和广度优先
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): def test(self): print('from D') class E(C): def test(self): print('from E') class F(D,E): # def test(self): # print('from F') pass f1=F() f1.test() print(F.__mro__) #只有新式才有这个属性可以查看线性列表,经典类没有这个属性 #新式类继承顺序:F->D->B->E->C->A #经典类继承顺序:F->D->B->A->E->C #python3中统一都是新式类 #pyhon2中才分新式类与经典类
继承原理:对于定义的每一个类,python会计算出一个方法解析顺序列表,这个MRO列表就是一个简单的所有基类的线性顺序列表。为了实现继承,python会在MRO列表上从左到右开始查找基类,直到找到第一个匹配的属性为止
子类中调用父类的方法
# 子类重用父类的两种方法 # 父类名.父类方法() class People: def __init__(self,name,age): self.name=name self.age=age def tell_info(self): print('name:%s---age:%s'%(self.name,self.age)) class Student(People): def __init__(self,name,age,sex): People.__init__(self,name,age) self.sex=sex stu1=Student('lary',18,'female') stu1.tell_info() #super() class People: def __init__(self,name,age): self.name=name self.age=age def tell_info(self): print('name:%s---age:%s'%(self.name,self.age)) class Student(People): def __init__(self,name,age,sex): super().__init__(name,age) #参照MRO列表检查父类,获取属性 self.sex=sex stu1=Student('lary',18,'female') stu1.tell_info()
多态与多态性
多态指的是同一类事物有多种形态
#多态 class Animal: #同一类事物:Animal pass class Pig(Animal): #Animal的形态之一:Pig def eat(self): print('Pig is eating') class Dog(Animal): #Animal的形态之二:Dog def eat(self): print('Dog is eating') #多态性:在不考虑对象具体类型的情况下,直接使用对象 pig1=Pig() dog1=Dog() pig1.eat() dog1.eat() #多态性的好处:增加了程序的灵活性和扩展性
鸭子类型:如果看起来像、叫声像而且走起路来像鸭子那么它就是鸭子
class Pig: def eat(self): print('Pig is eating') class Dog: def eat(self): print('Dog is eating') class Radio: def eat(self): print('Radio is eating')
多态性的好处
s=str('hello') l=list([1,2,3]) t=tuple((4,5,6)) print(len(s)) print(len(l)) print(len(t))
封装
属性隐藏
#在python中用双下划线开头的方式将属性隐藏起来(设置成私有的) #__开头的属性只是语法意义的变形,这种变形只在定义时发生一次,这种隐藏对外不对内 class Foo: __N=1 #将类的数据属性设置成私有的__N,在定义阶段会自动变形为_Foo__N def __init__(self,x,y): self.x=x self.__y=y def f1(self): print('f1') def f2(self): #在内部可以访问私有的数据 print("N:%s---Y:%s"%(self.__N,self.__y)) print(Foo.__dict__) obj=Foo(1,2) print(obj.f2()) print(obj.__dict__) obj.__z=5 print(obj.__dict__) print(obj.__z) #变形的过程只在类的定义阶段发生,在定义后的赋值操作不会变形 #在继承中,父类如果不想让子类覆盖自己的方法,可以将方法定义为私有 class Parent: def __f1(self): print('P f1') def f2(self): print('p f2') self.__f1() #只会与自己所在的类为准,即调用_Parent__f2 class sub(Parent): def __f1(self): print('s f1') s=sub() s.f2()
封装的意义:
封装数据属性的意义:将数据隐藏起来然后对外提供操作该数据的接口,在该接口上对该数据操作进行限制,以完成对数据属性操作的严格控制
#封装数据属性的意义 class People: def __init__(self,name,age): self.set_info(name,age) def tell_info(self): print("name:%s---age:%s"%(self.__name,self.__age)) def set_info(self,name,age): if type(name) is not str: raise TypeError('name must be str') self.__name=name self.__age=age p=People('lary',18) p.tell_info()
封装方法的意义:隔离复杂度(对于使用者来说只需要知道接口的功能,其余功能可以隐藏起来,这样既隔离了复杂度,也提升了安全性)
特性(property):property是一种特殊的属性,访问它时会执行一段功能(函数)然后返回值,这种特性的使用方式遵循了统一访问的原则
#特性(property) class People: def __init__(self,name,age,height,weight): self.name=name self.age=age self.height=height self.weight=weight @property def bmi(self): return self.weight/(self.height ** 2) egon=People('egon',18,1.80,75) print(egon.bmi)
class People: def __init__(self,name,age): self.__name=name self.__age=age @property def name(self): return self.__name @name.setter def name(self,obj): self.__name=obj @name.deleter def name(self): #del self.__name raise PermissionError ('no del') egon=People('egon',28) print(egon.name) egon.name='EGON' print(egon.name) del egon.name