面向对象之 —— 继承
OOP开发大致为:划分对象→抽象类→将类组织成为层次化结构(继承和合成) →用类与实例进行设计和实现。
继承与抽象(先抽象,再继承)
抽象:抽取多个类中相同的部分形成另一个类
通过抽象避免了继承一些不应该有的内容,抽象过程中,可能会有一些与业务无关的内容,这是正常的,这些是公共父类
继承描述的是子类与父类间关系,其基于抽象的结果
公共父类的作用:存储多个子类相同的属性和技能
继承
继承是一种关系,必须存在两个对象才能产生这种关系,被继承称为父(基/超类(Base class、Super class)),继承为子(派生类)。子类会“遗传”父类的属性,从而解决代码重用问题
程序中继承指的是类与类之间的关系,使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。继承的过程,就是从一般到特殊的过程。
class Parent: #父类 year = 2018 def coding(self): print("正在编程........") class Sub(Parent): #子类 pass print(Parent.year) print(Sub.year) # Sub.coding() s = Sub() print(s.year) # 子类可以使用父类中的属性 s.coding() # 子类也可以父类中的函数 #2018 正在编程
继承与重用性
在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时,我们不可能从头开始写一个类B,这就用到了类的继承的概念。通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用。
用已经有的类建立一个新的类,这样就重用了已经有的软件中的一部分设置大部分,这就是软件重用,不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,这样就是大大缩短了软件开发周期
注意:属性引用,会先从实例中找然后去类中找,然后再去父类中找...直到最顶级的父类。
在某些 OOP 语言中,一个子类可以继承多个基类。但是一般情况下,一个子类只能有一个基类,要实现多重继承,可以通过多级继承来实现。
单继承
class Person(Object): def __init__(self, name, age): self.name = name self.age = age def walk(self): print("%s is walking..." % self.name) def talk(self): print("%s is talking..." % self.name) # 定义子类1 py2\3都支持 class Teacher(Person): def __init__(self, name, age, level): super(Teacher, self).__init__(name, age) # python提供了这个函数,创建一个父类的对象,用来调用父类的__init__() # 避免了再次定义这些属性 self.level = level def walk(self): # 继承父类的方法并 拓展 实践中,继承的这种用途意义并不很大,甚至常常是有害的。因为它使得子类与基类出现强耦合。 super().walk() # 避免再写一遍父类中的功能代码 print("加速...") def teach(self): print("%s is teaching..." % self.name) # 定义子类2 class Student(Person): def __init__(self, name, age, grade): Person.__init__(self, name, age) # 这样写避免了自己再次定义这些属性 self.grade = grade def fight(self): print("%s is fighting..." % self.name) t1 = Teacher("mr z", 30, "一级") s1 = Student("xiao ming", 15, "高中一年级")
就像上述代码中,定义Person 类时,class Person(Object) 表示Person 类拥有Object 类的属性和方法(一些内置方法和属性),class Teacher(Person),Teacher 类拥有Person 类的属性和方法...(walk和talk). Person 是Teacher 的父类,Teacher 是Person 的子类.
Python 有两个判断继承的函数:
isinstance() 用于检查实例类型;issubclass() 用于检查类继承。
参见下方示例:
class Person(object): pass class Child(Person): # Child 继承 Person pass May = Child() Peter = Person() print(isinstance(May,Child)) # True print(isinstance(May,Person)) # True print(isinstance(Peter,Child)) # False print(isinstance(Peter,Person)) # True print(issubclass(Child,Person)) # True 类名.__bases__: 返回该类继承于哪些类(多继承时的从左到右的类都将被返回) 类名.__base__: 只返回从左到右继承的第一个类. 类名.__mro__ / 类名.mro():可以返回该类的查找顺序列表 在最上面的实例中 : print(Teacher.mro()) 打印[<class '__main__.Teacher'>, <class '__main__.Person'>, <class 'object'>]
多继承
多继承是指一个类有多个父类的情况(以,隔开)。多继承使用的比较少,但是需要知道,多继承时的继承顺序。
SubClass1.__bases__ #__base__只查看从左到右继承的第一个子类,__bases__则是查看所有继承的父类
1、Python的类可以继承多个类,Java和C#中则只能继承一个类
2、Python的类如果继承了多个类,那么其寻找方法的方式有两种,分别是:深度优先和广度优先
- 当类是经典类时,多继承情况下,会按照深度优先方式查找
- 当类是新式类时,多继承情况下,会按照广度优先方式查找
首先提出两个概念:新式类和经典类.
新式类:所有直接继承或间接继承object的类都是(python3中默认所有类都是新式类)。创建对象时需要申请名称空间,将对象属性放进去,这些事情在新式类里,都是objeck类自动执行
经典类:属性查找按照深度优先(python2中默认都是经典类)。继承于哪个类都需要在()里说明,没有显式的继承object类的类,以及该类的子类,都是经典类
python2与python3 的区别
1.只有在python2中才分新式类和经典类,python3中统一都是新式类 2.在python2中,没有显式的继承object类的类,以及该类的子类,都是经典类 3.在python2中,显式地声明继承object的类,以及该类的子类,都是新式类 4.在python3中,无论是否继承object,都默认继承object,即python3中所有类均为新式类
经典类与新式类: class S: pass class Student(S): pass # __bases__用于查看父类 print(Student.__bases__) # 显示属性的查找顺序列表,属性查找属性就是按照该列表来查找的 print(Student.mro())
Python 与其他语言不同点在于,当我们定义一个 class 的时候,我们实际上就定义了一种数据类型。我们定义的数据类型和Python自带的数据类型,比如str、list、dict没什么两样。由任意内置类型派生出的类(只要一个内置类型位于类树的某个位置),都属于“新式类”,都会获得所有“新式类”的特性.反之,即不由任意内置类型派生出的类,则称之为“经典类”。 “新式类”和“经典类”的区分在Python 3之后就已经不存在,在Python 3.x之后的版本,因为所有的类都派生自内置类型objec.(即使没有显示的继承object类型),即所有的类都是“新式类”。
经典列和新式类在继承顺序(继承的查找)方式上有区别:
- 新式类的继承顺序是按照C3算法来的
- 经典类则是按照“从左至右,深度优先”的方式去查找属性。当有菱形继承时,祖先类最后查找.
注意:在上述查找过程中,一旦找到,则寻找过程立即中断,便不会再继续找了
C3算法
merge操作是C3算法的核心。遍历执行merge操作的序列,如果一个序列的第一个元素,是其他序列中的第一个元素,或不在其他序列出现,则从所有执行merge操作序列中删除这个元素,合并到当前的mro中。
C3:合并所有父类的MRO列表并遵循如下三条准则: 1.子类会先于父类被检查 2.多个父类会根据它们在列表中的顺序被检查 3.如果对下一个类存在两个合法的选择,选择第一个父类
# 简易的定义几个类 class A(object): pass class B(object): pass class C(object): pass class E(A, B): pass class F(B, C): pass class G(E, F): pass ''' O表示object类 A、B、C都继承至一个基类,所以mro序列依次为[A,O]、[B,O]、[C,O] mro(E) = [E] + merge(mro(A), mro(B), [A,B]) = [E] + merge([A,O], [B,O], [A,B]) 执行merge操作的序列为[A,O]、[B,O]、[A,B] A是序列[A,O]中的第一个元素,在序列[B,O]中不出现,在序列[A,B]中也是第一个元素,所以从执行merge操作的序列([A,O]、[B,O]、[A,B])中删除A,合并到当前mro,[E]中。 mro(E) = [E,A] + merge([O], [B,O], [B]) 再执行merge操作,O是序列[O]中的第一个元素,但O在序列[B,O]中出现并且不是其中第一个元素。继续查看[B,O]的第一个元素B,B满足条件,所以从执行merge操作的序列中删除B,合并到[E, A]中。 mro(E) = [E,A,B] + merge([O], [O]) = [E,A,B,O] '''
mro
super获取父类内容时,按照mro列表来查找,mro列表的构造是通过C3线性算法实现的。
派生
子类继承某父类且有自己独特的属性/技能
一旦重新定义了自己的属性且与父类重名,那么调用的属性时,就以自己为准。
注意:在子类中,新建的重名的函数属性,在编辑函数内功能的时候,有可能需要重用父类中重名的那个函数功能,应该是用调用普通函数的方式,即:类名.func(),此时就与调用普通函数无异了,因此即便是self参数也要为其传值
子类出现了与父类重复的名字称之为覆盖。
子类访问父类中的方法
- I.指名道姓的调用,与继承无关
- II.super()表示创建一个特殊对象,用于调用父类的方法,其会参照类的mro列表一次查找属性
super().__init__(name,age,sex)
在python2中有不同方式,super需要传入当前类/对象
super(Student,self).__init__(name,age,sex).
存在继承关系后的属性查找
对象->类->父类->父类的父类
优先找对象,若对象没有,则找类,如果类没有,会沿着继承关系一直找到最顶层的父类
多层继承关系中深度优先,沿着一条继承路径找到底,若没有再找其他路径(按从左到右依次查找)
此情况仅在非菱形继承时使用
菱形
当存在共同父类时,会产生菱形继承关系
深度优先->广度优先
若父类中既有菱形又有非菱形,先按照继承顺序查找,若有菱形则按深度->广度
使用python3的新式类时,Object为默认的父类,所有的类都会继承它的一些属性和方法,它们的名称是以双下划线__开头,同时又以双下划线__结尾。
这些属性和方法不再是私有属性和私有方法,它们是可以在类的外部通过实例对象去直接访问的,且它们都有着各自特殊的意义,我们可以通过这些特殊属性和特殊方法来获取一些重要的信息,或执行一些有用的操作
属性:
属性名称 | 说明 |
__doc__ | 类的描述性信息 类似函数的''' ''' 注释 |
__module__ | 当前操作对象的类的定义所在模块名 |
__class__ | 当前操作的对象的类名 |
__dict__ | 一个字典,保存类的所有成员(属性和方法)或实例对象的成员属性 |
方法:
方法 | 说明 |
__init__ | 初始化构造方法,创建类时自动执行 |
__del__ | 析构方法,当对象在内存中被释放(回收)前,会自动执行 |
__str__ | 如果类中定义了__str__方法,并有返回值,在打印对象时会自动输出_-str__的返回值 |
__xxxitem__ |
是指__getitem__、__setitem__、__delitem这3个方法,它们用于索引操作,比如对字典的操作,分别表示 获取、设置、删除某个条目。 数据。可以通过这些方法来定义一个类对字典进行封装, 从而可以对字典中key的操作进行控制,尤其是删除操作。 |
__new__ | 该方法会在__init__方法之前被执行,该方法会创建被返回一个新的实例对象,然后传递给__init__。另外需要说明的是,这不是一个成员方法,而是一个静态方法。 |
__call__ | 源码中的注释是"Call self as a function." 意思是把自己(实例对象)作为一个函数去调用,而函数的调用方式是函数名() 。也就是说,当我们执行实例对象() 或者 类名()() 这样的操作时会触发执行该方法。 |
组合
软件重用的重要方式除了继承之外还有另外一种方式,即:组合
组合:在一个类中以另外一个类的对象作为数据属性,称为类的组合
组合是对象之间的关系
组合的目的:降低冗余,降低耦合度
组合与继承
1.继承的方式 通过继承建立了派生类与基类之间的关系,它是一种'是'的关系,比如白马是马,人是动物。 当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如老师是人,学生是人 2.组合的方式 用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,教授教python和linux课程,教授有学生s1、s2、s3... 当类之间有显著不同,并且较小的类是较大的类所需要的组件时,用组合比较好
class People: def __init__(self,name,age,sex): self.name=name self.age=age self.sex=sex class Course: def __init__(self,name,period,price): self.name=name self.period=period self.price=price def tell_info(self): print('<%s %s %s>' %(self.name,self.period,self.price)) class Teacher(People): def __init__(self,name,age,sex,job): People.__init__(self,name,age,sex) self.job_title=job_title self.course=[] self.students=[] class Student(People): def __init__(self,name,age,sex): People.__init__(self,name,age,sex) self.course=[] lh=Teacher('李华',38,'male',' 老师') xm=Student('小明',18,'female') python=Course('python','3mons',3000.0) linux=Course('python','3mons',3000.0) #为老师和学生添加课程 lh.course.append(python) lh.course.append(linux) xm.course.append(python) #为老师添加学生 lh.students.append(xm) #使用 for obj in lh.course: obj.tell_info()