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

微信公众号:码农充电站pro
个人主页:https://codeshellme.github.io

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

目录

在这里插入图片描述

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

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>",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

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

在这里插入图片描述

代码如下:

#! /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 能够表现出鸭子特性的原因。

(完。)

推荐阅读:

Python 简明教程 --- 16,Python 高阶函数

Python 简明教程 --- 17,Python 模块与包

Python 简明教程 --- 18,Python 面向对象

Python 简明教程 --- 19,Python 类与对象

Python 简明教程 --- 20,Python 类中的属性与方法

欢迎关注作者公众号,获取更多技术干货。

码农充电站pro

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Python中的函数(二) 在上一篇文章中提到了Python中函数的定义和使用,在这篇文章里我们来讨论下关于函数的一些更深的话题。在学习C语言函数的时候,遇到的问题主要有形参实参的区别、参数的传递和改变、变量的作用域。同样在Python中,关于对函数的理解和使用也存在这些问题。下面来逐一讲解。一.函
Python中的字符串 可能大多数人在学习C语言的时候,最先接触的数据类型就是字符串,因为大多教程都是以&quot;Hello world&quot;这个程序作为入门程序,这个程序中要打印的&quot;Hello world&quot;就是字符串。如果你做过自然语言处理方面的研究,并且用Python
Python 面向对象编程(一) 虽然Python是解释性语言,但是它是面向对象的,能够进行对象编程。下面就来了解一下如何在Python中进行对象编程。一.如何定义一个类 在进行python面向对象编程之前,先来了解几个术语:类,类对象,实例对象,属性,函数和方法。 类是对现实世界中一些事物的封装,
Python面向对象编程(二) 在前面一篇文章中谈到了类的基本定义和使用方法,这只体现了面向对象编程的三大特点之一:封装。下面就来了解一下另外两大特征:继承和多态。 在Python中,如果需要的话,可以让一个类去继承一个类,被继承的类称为父类或者超类、也可以称作基类,继承的类称为子类。并且Pytho
Python中的函数(一) 接触过C语言的朋友对函数这个词肯定非常熟悉,无论在哪门编程语言当中,函数(当然在某些语言里称作方法,意义是相同的)都扮演着至关重要的角色。今天就来了解一下Python中的函数用法。一.函数的定义 在某些编程语言当中,函数声明和函数定义是区分开的(在这些编程语言当中函数声明
在windows下如何快速搭建web.py开发框架 用Python进行web开发的话有很多框架供选择,比如最出名的Django,tornado等,除了这些框架之外,有一个轻量级的框架使用起来也是非常方便和顺手,就是web.py。它由一名黑客所创建,但是不幸的是这位创建者于2013年自杀了。据说现在由
将Sublime Text 2搭建成一个好用的IDE 说起编辑器,可能大部分人要推荐的是Vim和Emacs,本人用过Vim,功能确实强大,但是不是很习惯,之前一直有朋友推荐SUblime Text 2这款编辑器,然后这段时间就试了一下,就深深地喜欢上这款编辑器了...
Python中的模块 有过C语言编程经验的朋友都知道在C语言中如果要引用sqrt这个函数,必须用语句&quot;#include&lt;math.h&gt;&quot;引入math.h这个头文件,否则是无法正常进行调用的。那么在Python中,如果要引用一些内置的函数,该怎么处理呢?在Python中
Python的基础语法 在对Python有了基础的认识之后,下面来了解一下Python的基础语法,看看它和C语言、java之间的基础语法差异。一.变量、表达式和语句 Python中的语句也称作命令,比如print &quot;hello python&quot;这就是一条语句。 表达式,顾名思义,是
Eclipse+PyDevʽjango+Mysql搭建Python web开发环境 Python的web框架有很多,目前主流的有Django、Tornado、Web.py等,最流行的要属Django了,也是被大家最看好的框架之一。下面就来讲讲如何搭建Django的开发环境。一.准备工作 需要下载的
在windows下安装配置Ulipad 今天推荐一款轻便的文本编辑器Ulipad,用来写一些小的Python脚本非常方便。 Ulipad下载地址: https://github.com/limodou/ulipad http://files.cnblogs.com/dolphin0520/u...
Python中的函数(三) 在前面两篇文章中已经探讨了函数的一些相关用法,下面一起来了解一下函数参数类型的问题。在C语言中,调用函数时必须依照函数定义时的参数个数以及类型来传递参数,否则将会发生错误,这个是严格进行规定的。然而在Python中函数参数定义和传递的方式相比而言就灵活多了。一.函数参数的
在Notepad++中搭配Python开发环境 Python在最近几年一度成为最流行的语言之一,不仅仅是因为它简洁明了,更在于它的功能之强大。它不仅能够完成一般脚本语言所能做的事情,还能很方便快捷地进行大规模的项目开发。在学习Python之前我们来看一下Python的历史由来,&quot;Pytho
Python中的条件选择和循环语句 同C语言、Java一样,Python中也存在条件选择和循环语句,其风格和C语言、java的很类似,但是在写法和用法上还是有一些区别。今天就让我们一起来了解一下。一.条件选择语句 Python中条件选择语句的关键字为:if 、elif 、else这三个。其基本形式如
关于raw_input( )和sys.stdin.readline( )的区别 之前一直认为用raw_input( )和sys.stdin.readline( )来获取输入的效果完全相同,但是最近在写程序时有类似这样一段代码:import sysline = sys.stdin.readline()
初识Python 跟学习所有的编程语言一样,首先得了解这门语言的编程风格和最基础的语法。下面就让我们一起来了解一下Python的编程风格。1.逻辑行与物理行 在Python中有逻辑行和物理行这个概念,物理行是指在编辑器中实际看到的一行,逻辑行是指一条Python语句。在Python中提倡一个物理行只
当我们的代码是有访问网络相关的操作时,比如http请求或者访问远程数据库,经常可能会发生一些错误,有些错误可能重新去发送请求就会成功,本文分析常见可能需要重试的场景,并最后给出python代码实现。
1.经典迭代器 2.将Sentence中的__iter__改成生成器函数 改成生成器后用法不变,但更加简洁。 3.惰性实现 当列表比较大,占内存较大时,我们可以采用惰性实现,每次只读取一个元素到内存。 或者使用更简洁的生成器表达式 4.yield from itertools模块含有大量生成器函数可
本文介绍简单介绍socket的常用函数,并以python-kafka中的源码socketpair为例,来讲解python socket的运用
python实践中经常出现编码相关的异常,大多网上找资料而没有理解原理,导致一次次重复错误。本文对常用Unicode、UTF-8、GB2312编码的原理进行介绍,接着介绍了python字符类型unicode和str以及常见编解码错误UnicodeEncodeError和UnicodeDEcodeEr