zoukankan      html  css  js  c++  java
  • 类 与 继承

    什么是继承
    
    为什么需要继承
    
    存在继承后的属性查找顺序
    
    派生
    
    在子类重用父类方法
    
    经典类与新式类
    
    菱形继承的问题
    
    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) 补充: 抽象类中既可以包含抽象方法也可以包含普通方法和属性! 这和接口不同,接口仅仅是协议,所以接口中不应该包含任何具体的实现代码!
  • 相关阅读:
    Ajax基础:3.Json
    Head First Design Patterns State Pattern
    Head First Design Patterns Template Method Pattern
    Articles For CSS Related
    Head First Design Patterns Decorator Pattern
    代码审查工具
    How To Be More Active In A Group
    Head First Design Patterns Factory Method Pattern
    Head First Design Patterns Composite Pattern
    Tech Articles
  • 原文地址:https://www.cnblogs.com/Sunbreaker/p/11247131.html
Copyright © 2011-2022 走看看