Python 简明教程 --- 21,Python 继承与多态

栏目: IT技术 · 发布时间: 5年前

内容简介:微信公众号:码农充电站pro个人主页:程序不是年轻的专利,但是,它属于年轻。

微信公众号:码农充电站pro

个人主页: https://codeshellme.github.io

程序不是年轻的专利,但是,它属于年轻。

目录

Python 简明教程 --- 21,Python 继承与多态

我们已经知道 封装继承多态 是面向对象的三大特征,面向对象语言都会提供这些机制。

1,封装

在这一节介绍类的 私有属性和方法 的时候,我们已经讲到过 封装

封装 就是在设计一个类的时候,只允许使用者访问他需要的方法,将复杂的,没有必要让使用者知道的方法隐藏起来。这样,使用者只需关注他需要的东西,为其屏蔽了复杂性。

私有性 就是实现 封装 的一种手段,这样,类的设计者就可以控制类中的哪些属性和方法可以被使用者访问到。一般,类中的属性,和一些复杂的方法都不会暴露给使用者。

由于前边的章节介绍过封装,这里就不再举例说明了。

2,继承

通过 继承 的机制,可使得 子类 轻松的拥有 父类 中的 属性和方法继承 也是一种 代码复用 的方式。

Python 支持类的继承, 继承的类 叫做 子类 或者 派生类被继承的类 叫做 父类基类

继承的语法如下:

class 子类名(父类名):
    pass

子类名 后边的括号中,写入要继承的父类。

object

Python 的继承体系中, object 是最顶层类,它是所有类的父类。在定义一个类时,如果没有继承任何类,会默认继承 object 类。如下两种定义方式是等价的:

# 没有显示继承任何类,默认继承 object
class A1:
    pass

# 显示继承 object
class A2(object):
    pass

每个类中都有一个 mro 方法,该方法可以打印类的继承关系(顺序)。我们来查看 A1A2 的继承关系:

>>> A1.mro()
[<class '__main__.A1'>, <class 'object'>]
>>>
>>> A2.mro()
[<class '__main__.A2'>, <class 'object'>]

可见这两个类都继承了 object 类。

继承中的 __init__ 方法

当一个子类继承一个父类时,如果子类中没有定义 __init__ ,在创建子类的对象时,会调用父类的 __init__ 方法,如下:

#! /usr/bin/env python3

class A(object):

    def __init__(self):
        print('A.__init__')

class B(A):
    pass

以上代码中, B 继承了 AA 中有 __init__ 方法, B 中没有 __init__ 方法,创建类 B 的对象 b

>>> b = B()
A.__init__

可见 A 中的 __init__ 被执行了。

方法覆盖

如果类 B 中也定义了 __init__ 方法,那么,就只会执行 B 中的 __init__ 方法,而不会执行 A 中的 __init__ 方法:

#! /usr/bin/env python3

class A(object):

    def __init__(self):
        print('A.__init__')

class B(A):

    def __init__(self):
        print('B.__init__')

此时创建 B 的对象 b

>>> b = B()
B.__init__

可见,此时只执行了 B 中的 __init__ 方法。这其实是 方法覆盖 的原因,因为 子类 中的 __init__父类 中的 __init__ 的参数列表一样,此时,子类中的方法覆盖了父类中的方法,所以创建对象 b 时,只会执行 B 中的 __init__ 方法。

当发生继承关系(即一个子类继承一个父类)时,如果子类中的一个方法与父类中的一个方法 一模一样 (即方法名相同,参数列表也相同),这种情况就是 方法覆盖 (子类中的方法会覆盖父类中的方法)。

方法重载

方法名参数列表 都一样时会发生 方法覆盖 ;当 方法名 一样, 参数列表 不一样时,会发生 方法重载

在单个类中,代码如下:

#! /usr/bin/env python3

class A(object):

    def __init__(self):
        print('A.__init__')

    def test(self):
        print('test...')

    def test(self, i):
        print('test... i:%s' % i)

A 中的两个 test 方法, 方法名 相同, 参数列表 不同。

其实这种情况在 JavaC++ 是允许的,就是 方法重载 。而在Python 中,虽然在类中这样写不会报错,但实际上,下面的 test(self, i) 已经把上面的 test(self) 给覆盖掉了。创建出来的对象只能调用 test(self, i) ,而 test(self) 是不存在的。

示例:

>>> a = A()       # 创建 A 的对象 a
A.__init__
>>>
>>> a.test(123)   # 可以调用 test(self, i) 方法
test... i:123
>>>
>>> a.test()      # 调用 test(self) 发生异常
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: test() missing 1 required positional argument: 'i'

在继承关系中,代码如下:

#! /usr/bin/env python3

class A(object):

    def __init__(self):
        print('A.__init__')

    def test(self):
        print('test...')

class B(A):

    def __init__(self):
        print('B.__init__')

    def test(self, i):
        print('test... i:%s' % i)

上面代码中 B 继承了 ABA 中都有一个名为 test 的方法,但是 参数列表 不同。

这种情况跟在单个类中的情况是一样的,在类 B 中, test(self, i) 会覆盖A 中的 test(self) ,类 B 的对象只能调用 test(self, i) ,而不能调用 test(self)

示例:

>>> b = B()        # 创建 B 的对象
B.__init__
>>> 
>>> b.test(123)    # 可以调用 test(self, i)  方法
test... i:123
>>>
>>> b.test()       # 调用 test(self) 方法,出现异常
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: test() missing 1 required positional argument: 'i'

super() 方法

super() 方法用于调用父类中的方法。

示例代码:

#! /usr/bin/env python3

class A(object):

    def __init__(self):
        print('A.__init__')

    def test(self):
        print('class_A test...')

class B(A):

    def __init__(self):
        print('B.__init__')
        super().__init__()     # 调用父类中的构造方法

    def test(self, i):
        print('class_B test... i:%s' % i)
        super().test()         # 调用父类中的 test 方法

演示:

>>> b = B()          # 创建 B 的对象
B.__init__           # 调用 B 的构造方法
A.__init__           # 调用 A 的构造方法
>>>
>>> b.test(123)      # 调用 B 中的 test 方法 
class_B test... i:123
class_A test...      # 执行 A 中的 test 方法

is-a 关系

一个子类的对象,同时也是一个父类的对象,这叫做 is-a 关系。但是一个父类的对象,不一定是一个子类的对象。

这很好理解,就像,猫一定是动物,但动物不一定是猫。

我们可以使用 isinstance() 函数来判断一个对象是否是一个类的实例。

比如我们有如下两个类, Cat 继承了 Animal

#! /usr/bin/env python3

class Animal(object):
    pass

class Cat(Animal):
    pass

来看下对象和类之间的从属关系:

>>> a = Animal()           # 创建 Animal 的对象
>>> c = Cat()              # 创建 Cat 的对象
>>> 
>>> isinstance(a, Animal)  # a 一定是 Animal 的实例
True
>>> isinstance(c, Cat)     # c 一定是 Cat 的实例
True
>>> 
>>> isinstance(c, Animal)  # Cat 继承了 Animal,所以 c 也是 Animal 的实例
True
>>> isinstance(a, Cat)     # 但 a 不是 Cat 的实例
False

3,多继承

多继承 就是一个子类同时继承多个父类,这样,这个子类就同时拥有了多个父类的特性。

C++ 语言中允许多继承,但由于多继承会使得类的继承关系变得复杂。因此,到了 Java 中,就禁止了多继承的方式,取而代之的是,在Java 中允许同时继承多个 接口

Python 中也允许多继承,语法如下:

# 括号中可以写多个父类
class 子类名(父类1, 父类2, ...):
    pass

我们构造一个如下的继承关系:

Python 简明教程 --- 21,Python 继承与多态

代码如下:

#! /usr/bin/env python3

class A(object):
    def test(self):
        print('class_A test...')

class B(A):
    def test(self):
        print('class_B test...') 

class C(A):
    def test(self):
        print('class_C test...') 

class D(B, C):
    pass

ABC 中都有 test() 方法, D 中没有 test() 方法。

使用 D 类中的 mro() 方法查看继承关系:

>>> D.mro()
[<class 'Test.D'>, <class 'Test.B'>, <class 'Test.C'>, <class 'Test.A'>, <class 'object'>]

创建 D 的对象:

>>> d = D()

如果类 D 中有 test() 方法,那么 d.test() 肯定会调用 D 中的 test() 方法,这种情况很简单,不用多说。

当类 D 中没有 test() 方法时,而它继承的父类 BC 中都有 test() 方法,此时会调用哪个 test() 呢?

>>> d.test()
class_B test...

可以看到 d.test() 调用了类 B 中的 test() 方法。

实际上这种情况下,Python 解释器会根据 D.mro() 的输出结果来依次查找 test() 方法,即查找顺序是 D->B->C->A->object

所以 d.test() 调用了类 B 中的 test() 方法。

建议:

由于 多继承 会使类的继承关系变得复杂,所以并不提倡过多的使用 多继承

4,多态

多态 从字面上理解就是一个事物可以呈现多种状态。 继承 是多态的基础。

在上面的例子中,类 D 的对象 d 调用 test() 方法时,沿着 继承链D.mro() )查找合适的 test() 方法的过程,就是多态的表现过程。

比如,我们有以下几个类:

  • Animal :有一个 speak() 方法
  • Cat :继承 Animal 类,有自己的 speak() 方法
  • Dog :继承 Animal 类,有自己的 speak() 方法
  • Duck :继承 Animal 类,有自己的 speak() 方法

CatDogDuck 都属于动物,因此都继承 Animal ,代码如下:

#! /usr/bin/env python3

class Animal(object):
    def speak(self):
        print('动物会说话...')

class Cat(Animal):
    def speak(self):
        print('喵喵...') 

class Dog(Animal):
    def speak(self):
        print('汪汪...') 

class Duck(Animal):
    def speak(self):
        print('嘎嘎...') 

def animal_speak(animal):
    animal.speak()

我们还定义了一个 animal_speak 函数,它接受一个参数 animal ,在函数内,调用了 speak() 方法。

实际上,这种情况下,我们调用 animal_speak 函数时,可以为它传递 Animal 类型的对象,以及任何的 Animal 子类的对象。

传递 Animal 的对象时,调用了 Animal 类中的 speak()

>>> animal_speak(Animal())
动物会说话...

传递 Cat 的对象时,调用了 Cat 类中的 speak()

>>> animal_speak(Cat())
喵喵...

传递 Dog 的对象时,调用了 Dog 类中的 speak()

>>> animal_speak(Dog())
汪汪...

传递 Duck 的对象时,调用了 Duck 类中的 speak()

>>> animal_speak(Duck())
嘎嘎...

可以看到,我们可以给 animal_speak() 函数传递 多种不同类型 的对象,为 animal_speak() 函数传递不同类型的参数,输出了不同的结果,这就是 多态

5,鸭子类型

静态类型 语言中,有严格的类型判断,上面的 animal_speak() 函数的参数只能传递 Animal 及其 子类 的对象。

而Python 属于 动态类型 语言,不会进行严格的类型判断。

因此,我们不仅可以为 animal_speak() 函数传递 Animal 及其 子类 的对象,还可以传递其它与 Animal 类毫不相关的类的对象,只要该类中有 speak() 方法就行。

这种特性,在Python 中被叫做 鸭子类型 ,意思就是, 只要一个事物走起来像鸭子,叫起来像鸭子,那么它就是鸭子,即使它不是真正的鸭子

从代码上来说,只要一个类中有 speak() 方法,那么就可以将该类的对象传递给 animal_speak() 函数。

比如,有一个鼓类 Drum ,其中有一个函数 speak()

class Drum(object):
    def speak(self):
        print('咚咚...')

那么,类 Drum 的对象也可以传递给 animal_speak() 函数,即使 DrumAnimal 类毫不相关:

>>> animal_speak(Drum())
咚咚...

从另一个角度来考虑,实际上Python 函数中的参数,并没有标明参数的类型。在 animal_speak() 函数中,我们只是将参数叫做了 animal 而已,因此我们就认为 animal_speak() 函数应该接受Animal 类及其子类的对象,其实这仅仅只是我们认为的而已。

计算机并不知道 animal 的含义,如果我们将原来的 animal_speak() 函数:

def animal_speak(animal):
    animal.speak()

改写成:

def animal_speak(a):
    a.speak()

实际上,我们知道,这两个函数并没有任何区别。因此,参数 a 可以是任意的类型,只要 a 中有 speak() 方法就行。这就是Python 能够表现出 鸭子特性 的原因。

(完。)


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Defensive Design for the Web

Defensive Design for the Web

37signals、Matthew Linderman、Jason Fried / New Riders / 2004-3-2 / GBP 18.99

Let's admit it: Things will go wrong online. No matter how carefully you design a site, no matter how much testing you do, customers still encounter problems. So how do you handle these inevitable bre......一起来看看 《Defensive Design for the Web》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

html转js在线工具
html转js在线工具

html转js在线工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具