类 与 继承

Posted sunbreaker

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了类 与 继承相关的知识,希望对你有一定的参考价值。

什么是继承

为什么需要继承

存在继承后的属性查找顺序

派生

在子类重用父类方法

经典类与新式类

菱形继承的问题

mro列表

1.什么是继承
继承是一种关系,通过继承关系,一个对象可以直接使用另一个对象拥有的内容,例如王思聪继承王建林,王思聪就可以使用王健林拥有的财产!

被继承的一方称之为父,即王健林; 继承的一方称之为子,即王思聪

OOP继承描述的是两个类之间的关系,通过继承,一个类可以直接使用另一个类中已定义的方法和属性;

被继承的称之为父类或基类,继承父类的类称之为子类;

在python3中创建类时必然继承另一个类,如果没有显式的指定父类,则默认继承object类; object是根类 所有类都直接或间接的继承object

2.为什么需要继承
1.减少代码重复

2.为多态提供必要的支持,(关于多态下节会详细讨论!)

3.使用继承:
在类名后面的括号中指定要继承的父类名称?class 类名(父类名):

案例:在选课系统中,有老师和学生两种角色,老师拥有姓名,性别,年龄,学生也拥有姓名,性别,年龄,使用面向对象编程思想,可以将老师和学生定义为两个为不同的类

class Teacher:
    def __init__(self,name,gender,age):
        self.name = name
        self.gender = gender
        self.age = age
    def say_hi(self):
        print("hi my name is %s age is %s gender is %s" % (self.name,self.age,self.gender))
        
class Student:
    def __init__(self,name,gender,age):
        self.name = name
        self.gender = gender
        self.age = age
    def say_hi(self):
        print("hi my name is %s age is %s gender is %s" % (self.name,self.age,self.gender))
#创建两个对象
t1 = Teacher("Jack","man",20)
t1.say_hi()
s1 = Student("Maria","woman",20)
s1.say_hi()
两个类中的内容完全一致,则可以通过继承来重用代码

class Teacher:
    def __init__(self,name,gender,age):
        self.name = name
        self.gender = gender
        self.age = age
    def say_hi(self):
        print("hi my name is %s age is %s gender is %s" % (self.name,self.age,self.gender))

class Student(Teacher):  #指定Teacher类继承Student类
    pass

#创建两个对象
t1 = Teacher("Jack","man",20)
t1.say_hi()
s1 = Student("Maria","woman",20)
s1.say_hi()
4.继承与抽象
问题:
继承描述的是子类与父类之间的关系,在上面的例子中,Student继承Teacher完成了代码的重用,但是很明显老师类不是学生类的父类,学生类也不属于老师类,这样的继承关系在逻辑上是错误的;OOP的概念来自于现实世界,所以继承应当遵循现实世界的逻辑;

现在暂且不考虑逻辑错误,来看这样一个情况:

Teacher和Student由于存在相同的属性,为了减少重复代码,让两个逻辑上没有继承关系的类,产生了继承关系,如果后期Teacher类中增加了教学的方法,由于继承关系的存在,学生类也会拥有教学的方法,这是不合理的;

答:
应当将Teacher与Student中完全相同的部分抽取出来,放到另一个类中,并让Teacher与Student去继承它,这个类称之为公共父类 ,但是这个类与实际的业务需求是无关的在现实中也不实际存在,它的作用仅仅是存储相同代码以减少重复;这一过程我们称之为抽象;

综上所述,正确思路是:先抽象在继承

# 抽取老师和学生的相同内容 形成一个新的类,作为它们的公共父类
class Person:
    def __init__(self,name,gender,age):
        self.name = name
        self.gender = gender
        self.age = age
    def say_hi(self):
        print("hi my name is %s age is %s gender is %s" % (self.name,self.age,self.gender))
class Teacher(Person):    #指定Teacher类继承Person类
    pass
class Student(Person):  #指定Student类继承Person类
    pass

#创建两个对象
t1 = Teacher("Jack","man",20)
t1.say_hi()
s1 = Student("Maria","woman",20)
s1.say_hi()
抽象最主要的作用是划分类别(可以隔离关注点,降低复杂度),每个类之干自己的事情,多个类相同的事情交给父类来干

继承的另一种使用场景:

在开发程序的过程中,如果我们定义了一个类A,然后又想新建立另外一个类B,但是类B的大部分内容与类A的相同时

不需要从头开始写一个类B,这就用到了类的继承的概念。

通过继承的方式新建类B,让B继承A,B会‘遗传’A的所有属性(数据属性和函数属性),实现代码重用

用已经有的类建立一个新的类,这样就重用了已经有的软件中的一部分甚至大部分,大大节省了编程工作量,这就是常说的软件重用,不仅可以重用自己的类,也可以继承别人的,比如标准库,来定制新的数据类型,大大缩短了软件开发周期,对大型软件开发来说,意义重大

 

5.存在继承关系后的属性查找
一个类必然继承另一个类,被继承的类也有可能继承了其他类,相当于C继承B,B又继承A

此时查找属性的顺序是:

对象本身的名称空间 - > 类的名称空间 -> 父类的名称空间 -> 父类的父类名称空间 ->...object类

会沿着继承关系一直往后查找,直到找到为止,由于object是所有类的根类,所以如果找不着最后都会查找object类!

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.f1()
#输出 Bar.f1
b.f2()
#输出 Foo.f2
6.派生与覆盖
什么是派生
当父类提供的属性无法完全满足子类的需求时,子类可以增加自己的属性或非法,或者覆盖父类已经存在的属性,此时子类称之为父类的派生类;

什么是覆盖
在子类中如果出现于父类相同的属性名称时,根据查找顺序,优先使用子类中的属性,这种行为也称为覆盖

从Person类派生出来的Teacher类

# 抽取老师和学生的相同内容 形成一个新的类,作为它们的公共父类
class Person:
    def __init__(self,name,gender,age):
        self.name = name
        self.gender = gender
        self.age = age
    def say_hi(self):
        print("my name is %s age is %s gender is %s" % (self.name,self.age,self.gender))
class Teacher(Person):    #指定Teacher类继承Person类
    # Teacher类从Person类中继承到了say_hi方法 但是,老师打招呼时应当说出自己的职业是老师,所以需要
    # 定义自己的不同的实现方式
    def say_hi(self):
        print("hi i am a Teacher")
        #print("my name is %s age is %s gender is %s" % (self.name,self.age,self.gender))
        #上一行代码与父类中完全相同,可以直接调用父类提供的方法
        Person.say_hi(self)
# 创建Teacher对象
t1 = Teacher("Jack","man",20)
t1.say_hi()
#输出 hi i am a Teacher
#     my name is Jack age is 20 gender is man
在子类中,新建的重名的函数属性,在编辑函数内功能的时候,有可能需要重用父类中重名的那个函数功能,应该使用调用普通函数的方式,即:类名.func(),此时就与调用普通函数无异了,因此即便是self参数也要为其传值

 

7.子类中重用父类的方法
很多情况下 子类中的代码与父类中仅有小部分不同,却不得不在子类定义新的方法,这时候可以在子类中调用父类已有的方法,来完成大部分工作,子类仅需编写一小部分与父类不同的代码即可

在子类中有两种方式可以重用父类中的代码

1.使用类名直接调用 ,该方式与继承没有关系,即时没有继承关系,也可以调用

2.使用super()

class Vehicle: #定义交通工具类
     Country=China
     def __init__(self,name,speed,load,power):
         self.name=name
         self.speed=speed
         self.load=load
         self.power=power

     def run(self):
         print(开动啦...)

class Subway(Vehicle): #地铁
    def __init__(self,name,speed,load,power,line):
        #super(Subway,self) 就相当于实例本身 在python3中super()等同于super(Subway,self)
        super().__init__(name,speed,load,power)
        self.line=line

    def run(self):
        print(地铁%s号线欢迎您 %self.line)
        super(Subway,self).run()

class Mobike(Vehicle):#摩拜单车
    pass

line13=Subway(中国地铁,180m/s,1000人/箱,,13)
line13.run()
8.经典类与新式类
 

 

即使没有直接继承关系,super仍然会按照mro继续往后查找

而第一种方式明确指定了要到哪一个类中去查找,找不到则直接抛出异常

#A没有继承B,但是A内super会基于C.mro()继续往后找
class A:
    def test(self):
        super().test()
class B:
    def test(self):
        print(from B)
class C(A,B):
    pass

c=C()
c.test() #打印结果:from B


print(C.mro())
#[<class ‘__main__.C‘>, <class ‘__main__.A‘>, <class ‘__main__.B‘>, <class ‘object‘>]
*当你使用super()函数时,Python会在MRO列表上继续搜索下一个类。如果每个重定义的方法统一使用super()并只调用它一次,那么控制流最终会遍历完整个MRO列表,每个方法也只会被调用一次(注意注意注意:使用super调用的所有属性,都是从MRO列表当前的位置往后找,千万不要通过看代码去找继承关系,一定要看MRO列表)

 

9.组合
软件重用的重要方式除了继承之外还有另外一种方式,即:组合

组合指的是,在一个类中以另外一个类的对象作为数据属性,称为类的组合

class Equip: #武器装备类
     def fire(self):
         print(release Fire skill)

class Riven: #英雄Riven的类,一个英雄需要有装备,因而需要组合Equip类
     camp=Noxus
     def __init__(self,nickname):
         self.nickname=nickname
         self.equip=Equip() #用Equip类产生一个装备,赋值给实例的equip属性
r1=Riven(锐雯雯)
r1.equip.fire() #可以使用组合的类产生的对象所持有的方法
组合与继承都是有效地利用已有类的资源的重要方式。但是二者的概念和使用场景皆不同,

1.继承的方式

通过继承建立了派生类与基类之间的关系,它是一种的关系,比如白马是马,人是动物。

当类之间有很多相同的功能,提取这些共同的功能做成基类,用继承比较好,比如老师是人,学生是人

2.组合的方式

用组合的方式建立了类与组合的类之间的关系,它是一种‘有’的关系,比如教授有生日,教授教python和linux课程,教授有学生s1、s2、s3...

 

10.继承实现的原理
1.继承顺序
在Java和C#中子类只能继承一个父类,而Python中子类可以同时继承多个父类,如A(B,C,D)

如果继承关系为非菱形结构,则会按照先找B这一条分支,然后再找C这一条分支,最后找D这一条分支的顺序直到找到我们想要的属性

如果继承关系为菱形结构,那么属性的查找方式有两种,分别是:深度优先和广度优先

经典类查找顺序 深度优先  
新式类查找顺序 广度优先
2.继承实现原理 对于你定义的每一个类,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列表并遵循如下三条准则: 1.子类会先于父类被检查 2.多个父类会根据它们在列表中的顺序被检查 3.如果对下一个类存在两个合法的选择,选择第一个父类 11.接口(了解) 什么是接口? usb就是一种接口,电源插座也是接口,接口其实是一套协议规范; 为什么需要接口? 电脑提供USB接口,可以使用任何遵循USB接口协议的设备,其他设备只要按照USB协议的要求来设计产品,就能够被电脑使用,而电脑根本不需要关心这个设备具体是如何实现功能的 1.让使用者无需关心对象的类是什么,只需要的知道这些对象都具备某些功能就可以了,这极大地降低了使用者的使用难度。 2.使得外部使用者可以不加区分的处理所有接口兼容的对象 2.1:就好象linux的泛文件概念一样,所有东西都可以当文件处理,不必关心它是内存、磁盘、网络还是屏幕(当然,对底层设计者,当然也可以区分出“字符设备”和“块设备”,然后做出针对性的设计:细致到什么程度,视需求而定)。 2.2:再比如:我们有一个汽车接口,里面定义了汽车所有的功能,然后由本田汽车的类,奥迪汽车的类,大众汽车的类,他们都实现了汽车接口,这样就好办了,大家只需要学会了怎么开汽车,那么无论是本田,还是奥迪,还是大众我们都会开了,开的时候根本无需关心我开的是哪一类车,操作手法(函数调用)都一样 接口的使用 在python中根本就没有一个叫做interface的关键字,如果非要去模仿接口的概念 1.可以借助第三方模块: http://pypi.python.org/pypi/zope.interface 2.也可以使用继承来间接的实现接口 继承的两种用途 一:继承基类的方法,并且做出自己的改变或者扩展(代码重用); 二:声明某个子类兼容于某基类,定义一个接口类(模仿java的Interface),接口类中定义了一些接口名(就是函数名)但并未实现具体的功能,子类继承接口类,并且实现接口中的功能 class IOInterface:#定义接口Interface类来模仿接口的概念,python中压根就没有interface关键字来定义一个接口。 def read(self): #定接口函数read pass def write(self): #定义接口函数write pass class Txt(Interface): #文本,具体实现read和write def read(self): print(文本数据的读取方法) def write(self): print(文本数据的读取方法) class Sata(Interface): #磁盘,具体实现read和write def read(self): print(硬盘数据的读取方法) def write(self): print(硬盘数据的读取方法) class Process(Interface): def read(self): print(进程数据的读取方法) def write(self): print(进程数据的读取方法) 上面的代码只是看起来像接口,但是子类完全可以不用去实现接口,没有强制性的要求子类必须实现父类的方法,这就用到了抽象类 12.
抽象类 挪到多态 什么是抽象类 什么叫做抽象? 不具体,不清晰的就是抽象的,当我们知道某些对象具备一些功能,但是并不清楚这些功能是如何实现的,那对于我们而言这个功能就是抽象的; 抽象类也一样,如果这个类中的方法是不具体(没有实现功能的代码)的抽象的,那么这个类也是抽象的; 抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化,且有存在没有实现的方法; 为什么使用抽象类? 抽象类可以实现强制性要求子类必须实现父类声明的方法,这样一来只要一个类是这个抽象类的子类,那么他必然实现了抽象类中的方法,对于使用者而言,只要知道抽象类中的方法,就可以无差别的使用,这个抽象类的任何子类,大大降低了使用成本! 实战
#_*_coding:utf-8_*_ #一切皆文件 import abc #利用abc模块实现抽象类 class All_file(metaclass=abc.ABCMeta): all_type=file @abc.abstractmethod #定义抽象方法,无需实现功能 def read(self): 子类必须定义读功能 pass @abc.abstractmethod #定义抽象方法,无需实现功能 def write(self): 子类必须定义写功能 pass # class Txt(All_file): # pass # # t1=Txt() #报错,子类没有定义抽象方法 class Txt(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print(文本数据的读取方法) def write(self): print(文本数据的读取方法) class Sata(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print(硬盘数据的读取方法) def write(self): print(硬盘数据的读取方法) class Process(All_file): #子类继承抽象类,但是必须定义read和write方法 def read(self): print(进程数据的读取方法) def write(self): print(进程数据的读取方法) wenbenwenjian=Txt() yingpanwenjian=Sata() jinchengwenjian=Process() #这样大家的使用方法时完全一致的,也就是一切皆文件的思想 wenbenwenjian.read() yingpanwenjian.write() jinchengwenjian.read() print(wenbenwenjian.all_type) print(yingpanwenjian.all_type) print(jinchengwenjian.all_type) 补充: 抽象类中既可以包含抽象方法也可以包含普通方法和属性! 这和接口不同,接口仅仅是协议,所以接口中不应该包含任何具体的实现代码!

 

以上是关于类 与 继承的主要内容,如果未能解决你的问题,请参考以下文章

C++ 继承:父子类赋值转换菱形继承虚继承继承与组合

类 与 继承

继承派生新式类与经典类

3.继承与派生

C++菱形继承问题与虚拟继承原理

C++菱形继承问题与虚拟继承原理