[Python学习日记-66] 多态与多态性
简介
多态
多态性
鸭子类型
简介
多态与多态性都是面向对象的特征之一,它们都是面向对象编程的一个重要概念,在 Python 当中也有一些独特的见解和用法,下面我们一起来了解一下是怎么回事吧。
多态
多态指的是一类事物有多种形态,即指的是同一种操作作用于不同的对象,可以有不同的行为和结果,例如动物有多种形态:人、狗、猪
# 多态:同一类事物的多种形态
import abcclass Animal(metaclass=abc.ABCMeta): # 同一类事物:动物@abc.abstractmethoddef talk(self):passclass People(Animal): # 动物的形态之一:人def talk(self):print('say hello')class Dog(Animal): # 动物的形态之二:狗def talk(self):print('say wangwang')class Pig(Animal): # 动物的形态之三:猪def talk(self):print('say aoao')
再聚一个例子,以文件来说也有多种形态,例如文本文件、可执行文件
import abcclass File(metaclass=abc.ABCMeta): # 同一类事物:文件@abc.abstractmethoddef click(self):passclass Text(File): # 文件的形态之一:文本文件def click(self):print("open file")class ExeFile(File): # 文件的形态之二:可执行文件def click(self):print("execute file")
这就是类之间的多态了,多态在现实当中的例子多到数不胜数,但是了解完之后感觉好像没多大作用,其实真正的重头戏是在后面的多态性当中提现。
多态性
一、什么是多态动态绑定
多态性是指在不考虑实例类型的情况下使用实例,多态性分为静态多态性和动态多态性:
- 静态多态性:我们常用的运算符就是静态多态性,例如任何类型(int 和 list 等)都可以用运算符(+、-、*、/、% 等)进行运算
- 动态多态性:如下代码所示
peo1 = People()
dog1 = Dog()
pig1 = Pig()# peo1、dog1、pig1 都是动物,只要是动物肯定有 talk 方法
peo1.talk()
dog1.talk()
pig1.talk()
代码输出如下:
更进一步,我们可以以定义一个统一的接口来使用
def func(animal):animal.talk()func(peo1)
func(dog1)
func(dog1)
代码输出如下:
二、为什么要用多态性
其实大家从上面多态性的例子可以看出,我们并没有增加什么新的知识,也就是说 Python 本身就是支持多态性的,但这么做的好处是什么呢?它的好处如下:
1、增加了程序的灵活性
以不变应万变,无论对象千变万化,使用者都是用同一种形式去调用,例如 func(animal)
2、增加了程序的可扩展性
通过继承 animal 类创建了一个新的类,使用者无需更改自己的代码,还是用 func(animal) 去调用,如下
# 在上面的动物的代码的基础上增加一个 Cat 类
class Cat(Animal): # 动物的形态之四:猫def talk(self):print('say miaomiao')# 只需要实例化一个新的对象,之后调用 talk 方法的方式和未扩展之前的形式是一摸一样的,而且以前的代码都不需要改变def func(animal): # 对于使用者来说,自己的代码根本无需改动animal.talk()cat1 = Cat() # 实例出一只猫
func(cat1) # 甚至连调用方式也无需改变,就能调用猫的 talk 功能
代码输出如下:
这样我们新增了一个猫的形态,由 Cat 类产生的实例 cat1,使用者可以在完全不需要修改自己代码的情况下,使用和人、狗、猪一样的方式去调用 talk 方法。
鸭子类型
Python 是一个崇尚鸭子类型的语言,即“如果看起来像、叫声像而且走起路来像鸭子,那么它就是鸭子”的意思。
Python 程序员通常根据这种行为来编写程序。例如,如果想编写现有类的自定义版本,可以继承该类也可以创建一个外观和行为很像,但与它无任何关系的全新类,后者通常用于保存程序组件的松耦合度。下面我们举两个例子来看看
例1:利用标准库中定义的各种“与文件类似”的类,尽管这些类的工作方式像文件,但他们没有继承内置文件类的方法
# 二者都像鸭子,二看看起采都像文件,因面就可以当文件一样去用
class Disk:def read(self):print('disk read')def write(self):print('disk write')class Text:def read(self):print('text read')def write(self):print('text write')# 执行文件操作
# f = open(...) # 定义了一个文件对象
# f.read()
# f.write()disk = Disk()
text = Text()disk.read()
disk.write()text.read()
text.write()
代码输出如下:
上面的 Disk 和 Text 与我们之前学的文件操作的功能是一样的,都是有 read 和 write,,看起来和文件操作一模一样,但是实际上并不是都读文件(其中 Disk 就是读取硬盘的模拟操作),这就是鸭子类型。
例2:序列类型有多种形态:字符串(str)、列表(list)、元组(tuple),他们有相同的方法,但他们直接没有直接的继承关系
# 序列类型: 列表(list),元组(tuple),字符串(str)
l = list([1,2,3])
t = tuple(('a','b','c'))
s = str('hello')print(l.__len__())
print(t.__len__())
print(s.__len__())
代码输出如下:
上面三种类型的变量虽然都不同的序列,但是可以使用一样的函数,这是鸭子类型的一种体现,即字符串(str)、列表(list)、元组(tuple)这是三个独立的类,这三个类都有相同功能和名称函数。但其实上面的代码当中都只是普通的类的实例化成对象,而 Python 把它们都做得像序列一样,所以他们都被统称为序列类型,我们从源码看看这三个类到底有没有继承同一个叫做序列类型的父类,源码如下:
注意:这里的继承 object 类是由于 list、tuple、str 都是新式类,所以都会继承的,而在 Python3 中所有的类都是新式类
当然它们也有多态性的特性,可以使用统一的接口进行调用,如下
def len(obj):return obj.__len__()print(len(l))
print(len(t))
print(len(s))
代码输出如下:
其实这一种统一接口在 Python 中已经内置好了,我们平时使用时就无需自己写函数了,直接使用 len() 就可以了。
Python 之所以可以在不同的类之间使用相同的函数,而之间又没有继承关系,是因为 Python 崇尚鸭子类型,类与类之间只要做得像就行了,这体现了 Python 的多态性,这是一种理论层面的东西。鸭子类型总的来说就是看着像鸭子就行了,并不需要真正的使用父类来约束子类。