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

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

目录

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

1,封装

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

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

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

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

2,继承

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

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

继承的语法如下:

class 子类名(父类名):

pass

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

object 类

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

# 没有显示继承任何类,默认继承 object

class A1:

pass

# 显示继承 object

class A2(object):

pass

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

>>> A1.mro()

[, ]

>>>

>>> A2.mro()

[, ]

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

继承中的__init__ 方法

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

#! /usr/bin/env python3

class A(object):

def __init__(self):

print('A.__init__')

class B(A):

pass

以上代码中,B 继承了A,A 中有__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 方法,方法名相同,参数列表不同。

其实这种情况在Java 和 C++ 是允许的,就是方法重载。而在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 "", line 1, in

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 继承了A,B 和 A 中都有一个名为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 "", line 1, in

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

类A,B,C 中都有test() 方法,D 中没有test() 方法。

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

>>> D.mro()

[, , , , ]

创建D 的对象:

>>> d = D()

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

当类D 中没有test() 方法时,而它继承的父类 B 和 C 中都有 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() 方法

Cat,Dog,Duck 都属于动物,因此都继承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() 函数,即使Drum 与Animal 类毫不相关:

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

(完。)

推荐阅读:

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/564546.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++ new和delete运算符

C语言中,动态分配内存用 malloc() 函数,释放内存用 free() 函数。如下所示: int *p (int*) malloc( sizeof(int) * 10 ); //分配10个int型的内存空间 free(p); //释放内存在C中,这两个函数仍然可以使用,但是C又新增…

计划任务如何使用 java_java – 如何计划任务以定期间隔运行?

使用timer.scheduleAtFixedRatevoid java.util.Timer.scheduleAtFixedRate(TimerTask task, long delay, long period)scheduleAtFixedRatepublic void scheduleAtFixedRate(TimerTask task,long delay,long period)在指定的延迟后开始,为指定的任务调度重复的固定速…

C++ inline内联函数详解

函数是一个可以重复使用的代码块,CPU 会一条一条地挨着执行其中的代码。CPU 在执行主调函数代码时如果遇到了被调函数,主调函数就会暂停,CPU 转而执行被调函数的代码;被调函数执行完毕后再返回到主调函数,主调函数根据…

vc6.0mfc中单选按钮如何分组_按钮系列02-搞定按钮和选框的14个秘诀

UI 界面当中,各种开关、按钮、选框控件是非常常见的组件,它们看起来不复杂,但是在实际使用的时候讲究非常之多,它们不仅关乎体验,而且涉及到一些界面逻辑问题。英文中的 「Toggle」一词,对应的是带有短柄的…

C++函数的默认参数

在C中,定义函数时可以给形参指定一个默认的值,这样调用函数时如果没有给这个形参赋值(没有对应的实参),那么就使用这个默认的值。也就是说,调用函数时可以省略有默认值的参数。如果用户指定了参数的值&…

python复制文件夹不阻塞_python学习笔记-(十四)I/O多路复用 阻塞、非阻塞、同步、异步...

1. 概念说明1.1 用户空间与内核空间现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)。操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有…

java某个类避免findbug检查_Findbug插件静态java代码扫描工具使用

本文转自http://blog.csdn.net/gaofuqi/article/details/22679609 感谢作者FindBugs 是由马里兰大学提供的一款开源 Java静态代码分析工具。FindBugs通过检查类文件或 JAR文件,将字节码与一组缺陷模式进行对比从而发现代码缺陷,完成静态代码分析。FindBu…

Spring 是什么

Spring 是一个主流的 Java Web 开发框架,该框架是一个轻量级的应用框架,具有很高的凝聚力和吸引力。 Spring 是分层的 Java SE/EE full-stack 轻量级开源框架,以 IoC(Inverse of Control,控制反转)和 AOP&…

4乘4方格走的路线_苏州周边4个冷门自驾游路线景点推荐

1.崇明东滩崇明东滩湿地公园有两大看点,一是看候鸟迁徙,二是看日出和星空。东滩湿地滩涂辽阔,低头是大片的芦苇,抬头是满天斑斓的云彩,顺着木栈道走在公园内,如与大自然融为一体,可360度将美景收…

matlab程序转java_用面向对象的方法将一段JAVA代码转化为matlab

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼public class Variable {/*list of variables this variable is connected to. */ Vector neighbors;/* id of this variable */public int varID;/* id of agent this variable belongs to. */public int agentID;/** The domain …

Spring体系结构详解

Spring 框架采用分层架构,根据不同的功能被划分成了多个模块,这些模块大体可分为 Data Access/Integration、Web、AOP、Aspects、Messaging、Instrumentation、Core Container 和 Test。 Spring的体系结构如下图所示 图中包含了 Spring 框架的所有模块…

python时间戳是什么意思_Python-时间戳

# import time# 返回时间戳# print(time.time()) #返回时间戳# print(time.gmtime()) #返回utc时间戳# print(time.localtime(time.time()-86400)) #返回当地的时间戳# print(time.strftime("%Y-%m-%d",time.localtime())) #将struct_time转为字符串# print(time.mkt…

java对外查询接口注意的地方_Java接口注意点

1、接口可以多实现:一个实现类可以同时实现多个接口package com.qf.demo02_interface;//定义一种规则:interface A{public void testA();//public void fun();}//定义另一种规则interface B{public void testB();public void fun();}//实现类&#xff0c…

Spring目录结构和基础JAR包介绍

目前 Spring 框架的最新版本是 5.1.8,本教程是基于 Spring 的稳定版本 3.2.13 进行讲解的。读者可以通过网址 http://repo.spring.io/simple/libs-release-local/org/springframework/spring/ 下载名称为 springframework-3.2.13.RELEASE-dist.zip 的压缩包。单击此…

自定义依赖注解无效_SpringValidation用注解代替代码参数校验解析

Spring Validation概念在原先的编码中,我们如果要验证前端传递的参数,一般是在接受到传递过来的参数后,手动在代码中做 if-else 判断,这种编码方式会带来大量冗余代码,十分的不优雅。因此,推出了用注解的方…

mysql开源许可_为什么开源数据库改变许可证?

CockroachDB 是一个开源的分布式数据库,最近改变了代码授权,放弃了 Apache 许可证。一、CockroachDB 的许可证变更CockroachDB 以前的许可证是 Apache,代码托管在 GitHub,任何人都可以访问。现在的许可证改成了"商业源码许可…

Spring IoC容器

我们将详细介绍 Spring 的 Ioc 容器。 IoC 是指在程序开发中,实例的创建不再由调用者管理,而是由 Spring 容器创建。Spring 容器会负责控制程序之间的关系,而不是由程序代码直接控制,因此,控制权由程序代码转移到了 S…

python相同怎么写_这两个index相同的dataframe我想把他们merge,怎么写?

能具体点么?这是这两df的代码# -*- coding: utf-8 -*-import pandas as pdimport numpy as npimport matplotlib.pyplot as pltimport tracebackfrom tools import *import matplotlib as mplimport pickleimport datetime as dtdf1 pd.DataFrame({A0: [A0, A1, …

python里删除range里的数字_python中range函数与列表中删除元素

一、range函数使用range(1,5) 代表从1到4(不包含5),结果为:1,2,3,4 ,默认步长为1range(1,5,2) 结果为:1, 3 (同样不包含5) ,步长为2range(5,-1,-1) 反向输出,结果为:5,4,3,2,1,0 &#…

python中print又可将数据写入文件_Python第五课-将写入文件的列表格式化

1、上节课代码中的问题 第四课中介绍了python持久化的基础实例——将数据写入文本文件。 可当我们试着把保存数据的文件读取出来会怎样呢? try: with open(man.txt, r) as fman: print(fman.readline())except IOError as err: print(str(err)) 执行时,1…