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

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

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

目录

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

1,封装

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

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

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

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

2,继承

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

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

继承的语法如下:

class 子类名(父类名):
pass

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

object 类

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

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

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

>>> 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 继承了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 "<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 继承了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 "<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

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

代码如下:

#! /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()
[<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() 方法时,而它继承的父类 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 能够表现出鸭子特性的原因。

(完。)


推荐阅读:

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

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

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

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

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


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

Python 简明教程 --- 21,Python 继承与多态的更多相关文章

  1. Python 简明教程 --- 22,Python 闭包与装饰器

    微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 当你选择了一种语言,意味着你还选择了一组技术.一个社区. 目录 本节我们来介绍闭包与装饰器. 闭包与 ...

  2. Python 简明教程 --- 23,Python 异常处理

    微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 要么做第一个,要么做最好的一个. 目录 我们在编写程序时,总会不自觉的出现一些错误,比如逻辑错误,语 ...

  3. Python 简明教程 --- 24,Python 文件读写

    微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 过去的代码都是未经测试的代码. 目录 无论是哪种编程语言,IO 操作都是非常重要的部分.I 即Inp ...

  4. Python 简明教程 --- 25,Python 目录操作

    微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 做技术一定要一颗恒心,这样才不会半途而废. 目录 上一节我们介绍了文件相关的操作,本节我们来介绍目录 ...

  5. Python 简明教程 --- 26,Python 多进程编程

    微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 学编程最有效的方法是动手敲代码. 目录 1,什么是多进程 我们所写的Python 代码就是一个程序, ...

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

    微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 代码能借用就借用. -- Tom Duff 目录 编程可分为面向过程编程和面向对象编程,它们是两种不 ...

  7. 【笔记】Python简明教程

    Python简明教程,此资源位于http://woodpecker.org.cn/abyteofpython_cn/chinese/ s=u'中文字符' #u表示unicode,使用u之后能正常显示中 ...

  8. Python 简明教程 --- 8,Python 字符串函数

    微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 好代码本身就是最好的文档.当你需要添加一个注释时,你应该考虑如何修改代码才能不需要注释. -- St ...

  9. Python 简明教程 --- 5,Python 表达式与运算符

    微信公众号:码农充电站pro 个人主页:https://codeshellme.github.io 靠代码行数来衡量开发进度,就像是凭重量来衡量飞机制造的进度. -- Bill Gates 目录 1, ...

随机推荐

  1. java实现圆周率与级数

    ** 圆周率与级数** 圆周率 我国古代数学家对圆周率方面的研究工作,成绩是突出的.三国时期的刘徽.南北朝时期的祖冲之都在这个领域取得过辉煌战绩. 有了计算机,圆周率的计算变得十分容易了.如今,人们创 ...

  2. 关于C#委托三种调用的分享

    一.同步调用 1.同步调用会按照代码顺序来执行2.同步调用会阻塞线程,如果是要调用一项繁重的工作(如大量IO操作),可能会让程序停顿很长时间,造成糟糕的用户体验,这时候异步调用就很有必要了. 举个栗子 ...

  3. Tomcat 配置必备的 10 个小技巧

    现在开发Java Web应用,建立和部署Web内容是一件很简单的工作.使用Jakarta Tomcat作为Servlet和JSP容器的人已经遍及全世界.Tomcat具有免费.跨平台等诸多特性,并且更新 ...

  4. 朋友,您可能是MCR的受害者

    2018 年五月之后,微软将后续发布的所有 docker image 都推送到了 MCR (Miscrosoft Container Registry),但在中国大陆,它的速度实在是令人发指,本文将介 ...

  5. SpringBoot 2.3 整合最新版 ShardingJdbc + Druid + MyBatis

    今天项目不忙,想搞一下shardingJDBC分库分表看看,主要想实现以下几点: 舍弃xml配置,使用.yml或者.properties文件+java的方式配置spring. 使用 Druid 作为数 ...

  6. 测试-spring源码摘取

    首次加载idea是真的慢... 也许是我电脑性能太差... 我分析他内部有一套索引的机制,需要每次打开现建立... 没有固态的话,首次打开还不如eclipse 第一次使用博客园,以前都在csdn混~ ...

  7. JavaScript选择器和节点操作

    感谢:链接(视频讲解很清晰) 下文中讲解用到Chrome中的console调试台,如果不懂最好先看一下:链接 JavaScript选择器 作用:选取html中的标签等内容,最重要的还是为节点的操作(增 ...

  8. 多线程高并发编程(10) -- ConcurrentHashMap源码分析

    一.背景 前文讲了HashMap的源码分析,从中可以看到下面的问题: HashMap的put/remove方法不是线程安全的,如果在多线程并发环境下,使用synchronized进行加锁,会导致效率低 ...

  9. 特性速览| Apache Hudi 0.5.3版本正式发布

    1. 下载连接 源代码下载:Apache Hudi 0.5.3 Source Release (asc, sha512) 0.5.3版本相关jar包地址:https://repository.apac ...

  10. Linux下搭建mysql

    [准备环境] Linux   centos7 [mysql安装步骤] 1.首先确定centos版本 cat /etc/redhat-release 2.yum安装 yum -y install mar ...