22.Python编程:封装、继承、多态

作者: TensorFlow开发者 | 来源:发表于2018-04-22 14:57 被阅读71次

    前面学习了面向过程和面向对象的概念,又学习了Python中类和对象的相关知识,其中面向对象最大的特征是:封装、继承和多态。本文就分3大块内容,分别来详细学习面向对象中的三大特征。

    封装

    封装的概念来自电气电子元件,集成电路的一道工序,后来引用到面向对象的程序设计思想中。现实中处处可见封装的例子:比如你家的洗衣机。

    把一台洗衣机看做洗衣机类的一个实例,洗衣机里有标准洗、速洗、精洗、桶干燥、洁桶等多种功能。作为用户不需要知道这些功能内部的具体实现,需要某项功能只要选择对应的功能键即可。这就是洗衣机封装了以上多种功能。

    洗衣机-封装

    相似地,在面向对象中,封装,说白了就是一个类中抽象出来的静态数据(即属性)以及该类的一些功能(即方法)。这就是前面学习的对象的属性和方法。在一个对象内部,某些代码或某些数据可以是私有的,不能被外界访问。通过这种方式,对象对内部数据提供了不同级别的保护,以防止程序中无关的部分意外的改变或错误的使用了对象的私有部分。这是前面学习的访问权限,在封装特性中,访问权限非常重要。

    继承

    在现实生活中一说到继承,你一定会想到"子承父业"这个词。因为这种父子关系,儿子"平白无故地"从父亲那里得到了家产基业等。

    在面向对象的程序设计里,与此类似。假设A类继承自B类,则A类称为子类或派生类,B类称为父类,也叫基类、超类。那么A类就"子承父业",这里的"父业",就是父类中的属性和方法,如此,A类中自然就有了B类中的属性和方法。这种继承关系使得代码的复用性大大提高。

    Python 同样支持类的继承,如果一种语言不支持继承,类就没有什么意义。不同的开发语言,继承的支持程度不一样。Objective-C、Java等是单继承,而Python、C++等是支持多继承的。

    前面在学习python中类的定义时,当时这样解释说明:类的定义需要关键字class,class后面紧接着是类名,即ClassName,类名通常是大写开头的单词。紧接着是(object),表示该类是从哪个类继承下来的,通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。<statement-1-N>表示类中的属性或方法。

    class ClassName(object):
        <statement-1>
        ·
        ·
        ·
        <statement-N>
    

    既然,python是支持多继承的,那么类名后面的()里,是可以有多个类的,即:(BaseClass1, BaseClass2, BaseClass3...)。需要注意圆括号中父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索,即方法在子类中未找到时,从左到右查找基类中是否包含方法。注意:方法的查找顺序这一点要十分清楚。

    BaseClassName(基类名)这种写法必须与派生类定义在一个作用域内。通常,如果基类定义在另一个模块中时这一点非常有用:
    class ClassName(modname.BaseClassName):

    我们假设定义了三个类:Animal动物类,Dog狗类,Cat猫类,模块结构分别如下:


    单继承模块结构

    Animal源码如下:

    # 定义一个动物类Animal
    class Animal(object):
        legs = ''  # 腿的数量
        color = ''  # 毛色
    
        def eat(self):
            print("Animal吃东西")
    
        def sleep(self):
            print("Animal睡觉")
    
        def walk(self):
            print("Animal走路")
    

    Dog狗类,继承自Animal类,源码:

    from extends import animal
    
    # 定义一个狗类,继承自动物类Animal
    class Dog(animal.Animal):
        pass
    
    

    Cat猫类继承自Animal类,源码如下:

    from extends import animal
    
    # 定义一个猫类Cat,继承自动物类Animal
    class Cat(animal.Animal):
        pass
    
    

    测试模块extends_test.py,分别创建了一个猫类对象c、狗类对象d,分别调用c、d对象吃的方法eat(),源码如下:

    from extends import cat, dog
    
    # 创建一个猫类对象
    c = cat.Cat()
    # 调用吃的方法
    c.eat()
    
    print('------------------')
    
    # 创建一个狗类对象
    d = dog.Dog()
    # 调用吃的方法
    d.eat()
    

    运行结果:

    Animal吃东西
    ------------------
    Animal吃东西
    

    可以看到,都打印出了Animal类中eat()方法中的内容。这样我们通过继承关系,让Dog\Cat两个类什么也不写就有了父类中的所有的属性和方法。


    多继承

    我们假设定义了4个类:Animal动物类,哺乳动物类Mammal、Dog狗类,Cat猫类,模块结构分别如下:


    多继承模块结构

    Animal动物类,哺乳动物类Mammal两个类,将作为Dog狗类,Cat猫类的父类。

    Animal源码如下:

    # 定义一个动物类Animal
    class Animal(object):
        legs = ''  # 腿的数量
        color = ''  # 毛色
    
        def eat(self):
            print("Animal吃东西")
    
        def sleep(self):
            print("Animal睡觉")
    
        def walk(self):
            print("Animal走路")
    

    Mammal哺乳动物类,源码如下:

    # 定义一个动物类Mammal
    class Mammal(object):
    
       # 哺育幼崽的方法
       def feed(self):
           print("用母乳哺育幼崽子")
    

    Dog狗类,继承自Animal类,源码如下:

    from extends import animal, mammal
    
    # 定义一个狗类,继承自动物类Animal,哺乳动物类Mammal
    class Dog(animal.Animal, mammal.Mammal):
        pass
    

    Cat猫类继承自Animal类,源码如下:

    from extends import animal, mammal
    
    # 定义一个猫类Cat,继承自动物类Animal,哺乳动物类Mammal
    class Cat(animal.Animal, mammal.Mammal):
        pass
    

    测试模块extends_test.py,分别创建了一个猫类对象c、狗类对象d,分别调用c、d对象吃的方法eat(),源码如下:

    from extends import cat, dog
    
    # 创建一个猫类对象
    c = cat.Cat()
    # 调用吃的方法
    c.eat()
    
    # 调用Mammal类中的方法
    c.feed()
    
    print('------------------')
    
    # 创建一个狗类对象
    d = dog.Dog()
    # 调用Animal中吃的方法:eat()
    d.eat()
    
    # 调用Mammal类中的方法:feed()
    d.feed()
    

    运行结果:

    Animal吃东西
    用母乳哺育幼崽子
    ------------------
    Animal吃东西
    用母乳哺育幼崽子
    
    拓展

    在Java中,使用extend关键字表示继承,Object是Java中所有类的基类,仅支持单继承。例如:

    class A extend Object{
    
    }
    
    

    在Objective-C中,则使用:表示继承。NSObject是OC中所有类的基类,且仅支持单继承。例如:

    @interface MyClass : NSObject {
        int memberVar1; // 实体变量
        id  memberVar2;
    }
    
    +(return_type) class_method;     // + 开头表示类方法
    -(return_type) instance_method1; // - 开头表示实例方法
    

    接下来要学习的面向对象的多态,正是在继承的基础上才有的特性:当子类和父类有了相同名称的方法时,就是多态了。

    多态

    多态特性涉及到父子类以及父类之间同名函数的调用顺序问题,所以前面提到了:一定要注意Python在自定义类时,类名后面的()圆括号中父类的顺序。若是父类中有相同的方法名,而在子类使用时未指定,python从左至右搜索,即方法在子类中未找到时,从左到右查找基类中是否包含方法。

    注意:方法的查找顺序这一点要十分清楚。

    使用上面同样的类,我们定义了4个类:Animal动物类,哺乳动物类Mammal、Dog狗类,Cat猫类,模块结构分别如下:


    多态-模块结构

    Animal动物类,哺乳动物类Mammal两个类,将作为Dog狗类,Cat猫类的父类。

    Animal源码如下:

    # 定义一个动物类Animal
    class Animal(object):
        legs = ''  # 腿的数量
        color = ''  # 毛色
    
        def eat(self):
            print("Animal吃东西")
    
        def sleep(self):
            print("Animal睡觉")
    
        def walk(self):
            print("Animal走路")
    

    Mammal哺乳动物类,源码如下:

    # 定义一个动物类Mammal
    class Mammal(object):
    
       def feed(self):
           print("用母乳哺育幼崽子")
           
       def eat(self):
           print("哺乳动物-吃东西")
    
       def sleep(self):
           print("哺乳动物-睡觉")
    
       def walk(self):
           print("哺乳动物-走路")
    

    Dog狗类,继承自Animal类,源码如下:

    from extends import animal, mammal
    
    
    # 定义一个狗类,继承自动物类Animal,哺乳动物类Mammal
    class Dog(animal.Animal, mammal.Mammal):
    
        def eat(self):
            print("小狗啃骨头")
    
        def sleep(self):
            print("小狗警惕地睡觉")
    
        def walk(self):
            print("小狗走路")
    
    

    Cat猫类继承自Animal类,源码如下:

    from extends import animal, mammal
    
    
    # 定义一个猫类Cat,继承自动物类Animal,哺乳动物类Mammal
    class Cat(animal.Animal, mammal.Mammal):
    
        def eat(self):
            print("小猫吃鱼")
    
        def sleep(self):
            print("小猫呼呼睡觉")
    
        def walk(self):
            print("小猫走猫步")
    
    

    可以看到,Animal动物类,哺乳动物类Mammal两个类,作为Dog狗类,Cat猫类的父类,并且每个类中都有同名不同实现的方法:eat()、sleep()、walk()

    此时再在测试模块extends_test.py,分别创建了一个猫类对象c、狗类对象d,分别调用c、d对象吃的方法eat(),源码如下:

    from extends import cat, dog
    
    # 创建一个猫类对象
    c = cat.Cat()
    # 调用吃的方法
    c.eat()
    
    print('------------------')
    
    # 创建一个狗类对象
    d = dog.Dog()
    # 调用Animal中吃的方法:eat()
    d.eat()
    

    运行结果如下:

    小猫吃鱼
    ------------------
    小狗啃骨头
    

    子类的方法覆盖了父类中同名的方法,优先调用子类中同名的方法。

    假设,现在需要调用父类中的方法,而不是调用子类中的方法,则可以这么做:

    # 上接
    
    # 调用d对象父类中的eat()方法
    super(dog.Dog, d).eat()
    

    运行结果:

    Animal吃东西
    

    深入理解多态

    要理解什么是多态,我们首先要对数据类型再作一点说明。当我们定义一个class的时候,我们实际上就定义了一种数据类型。我们定义的数据类型和Python自带的数据类型,比如str、list、dict没什么两样。比如上面的用到的对象:

    my_list = list() # my_list 是list类型
    a = Animal() # a是Animal类型
    d = Dog() # d是Dog类型
    
    isinstance(对象, 类型)

    isinstance(变量, 类型)用来判断一个变量是否是该类型或其子类的实例。注意如果该对象是该类的子类的对象也返回True。

    # 创建一个猫类对象
    c = cat.Cat()
    
    # 判断猫对象c是否是Cat类型
    print('猫c是Cat类型:',isinstance(c, cat.Cat))
    
    # 判断猫对象c是否是Cat类型
    print('猫c是Animal类型:',isinstance(c, animal.Animal))
    

    运行结果:

    猫c是Cat类型: True
    猫c是Animal类型: True
    

    这和现实是匹配的:猫是猫类型,猫是动物。但是,反过来,动物是猫,就不行了。

    # 创建一个Animal动物对象
    a = animal.Animal()
    
    # 判断动物对象a是否是Cat类型
    print('动物a是Cat类型:', isinstance(a, cat.Cat))
    
    # 判断动物对象a是否是Animal类型
    print('动物a是Animal类型:', isinstance(a, animal.Animal))
    

    运行结果:

    动物a是Cat类型: False
    动物a是Animal类型: True
    

    知道了以上知识,我们再写一个方法:

    # 定义一个动物吃东西的方法
    def animal_eat(animal):
        animal.eat()
    
    # 调用动物吃东西的方法,传入不同的对象
    animal_eat(dog.Dog())
    animal_eat(cat.Cat())
    

    运行结果:

    小狗啃骨头
    小猫吃鱼
    

    至此发现什么没?如果没有,那么现在,如果我们再定义一个Wolf类型,也继承自Animal:

    from extends import animal, mammal
    
    
    # 定义一个狼类,继承自动物类Animal,哺乳动物类Mammal
    class Wolf(animal.Animal, mammal.Mammal):
    
        def eat(self):
            print("狼吃羊")
    
        def sleep(self):
            print("狼警惕地睡觉")
    
        def walk(self):
            print("狼飞奔")
    

    在测试模块extends_test.py,导入wolf模块,并创建一个wolf对象,源码如下:

    from extends import cat, dog, animal, wolf
    
    
    # 定义一个动物吃东西的方法
    def animal_eat(animal):
        animal.eat()
    
    animal_eat(dog.Dog())
    animal_eat(cat.Cat())
    
    # 传入狼对象
    animal_eat(wolf.Wolf())
    

    运行结果:

    小狗啃骨头
    小猫吃鱼
    狼吃羊
    

    你会发现,新增一个Animal的子类,不必对animal_eat()做任何修改,实际上,任何依赖Animal作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。

    多态的好处

    多态的好处就是,当我们在需要传入Dog、Cat、Wolf……时,我们只需要接收Animal类型就可以了,因为Dog、Cat、Wolf……都是Animal类型,然后,按照Animal类型进行操作即可。由于Animal类型有eat()方法,因此,传入的任意类型,只要是Animal类或者子类,就会自动调用实际类型的eat()方法,这就是多态的好处。

    对于一个变量,我们只需要知道它是Animal类型,无需确切地知道它的子类型,就可以放心地调用eat()方法,而具体调用的eat()方法是作用在Animal、Dog、Cat还是Wolf对象上,由运行时该对象的确切类型决定,这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保eat()方法编写正确,不用管原来的代码是如何调用的。

    拓展:静态语言 & 动态语言

    对于静态语言来说,例如Java,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用eat()方法。

    对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个eat()方法就可以了。例如,定义一个带有eat()方法的黑洞类:

    # 定义一个带有eat()方法的黑洞类
    class BlackHole(object):
        def eat(self):
            print("黑洞吞噬一切东西,包括光~")
    
    from extends import cat, dog, animal, wolf
    
    
    # 定义一个动物吃东西的方法
    def animal_eat(animal):
        animal.eat()
    
    # 传入黑洞对象
    animal_eat(BlackHole())
    

    运行结果:

    黑洞吞噬一切东西,包括光~
    

    这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。

    Python的“file-like object“就是一种鸭子类型。对真正的文件对象,它有一个read()方法,返回其内容。但是,许多对象,只要有read()方法,都被视为“file-like object“。许多函数接收的参数就是“file-like object“,你不一定要传入真正的文件对象,完全可以传入任何实现了read()方法的对象。

    小结

    本文详细学习了Python中面向对象的3大特性:封装、继承、多态,以及拓展了python中的鸭子类型。这三大特性是任何面向对象语言的基础知识,要深刻理解。


    更多了解,可关注公众号:人人懂编程


    微信公众号:人人懂编程

    相关文章

      网友评论

        本文标题:22.Python编程:封装、继承、多态

        本文链接:https://www.haomeiwen.com/subject/hdqqlftx.html