Python实现多态、协议和鸭子类型的代码详解

yipeiwu_com6年前Python基础

多态

问起面向对象的三大特性,几乎每个人都能对答如流:封装、继承、多态。今天我们就要来说一说 Python 中的多态。

所谓多态:就是指一个类实例的相同方法在不同情形有不同表现形式。多态机制使具有不同内部结构的对象可以共享相同的外部接口。这意味着,虽然针对不同对象的具体操作不同,但通过一个公共的类,它们(那些操作)可以通过相同的方式予以调用。

我在《Python 中的设计模式详解之:策略模式》一文中详细描述了策略模式的实现,而策略模式就是典型的多态应用。

之前的代码我就不贴了,大家可以去原文中查看。我依然还是以商品折扣的经典举例。策略模式一文中,传统的策略模式实现方式我也是用 Python 代码实现的,在 java 或 C# 等语言中,实现方式也差不多。以下是 C# 代码,我只列了个架子:

interface Promotion
{
 double discount(Order order);
}
class FidelityPromo : Promotion // 第一个具体策略
{
 // 为积分为1000或以上的顾客提供5%折扣
 public double discount(Order order)
 {
  ...
 }
}
class BulkItemPromo : Promotion // 第二个具体策略
{
 //单个商品为20个或以上时提供10%折扣
 public double discount(Order order)
 {
  ...
 }
}
class LargeOrderPromo : Promotion // 第三个具体策略
{
 //订单中的不同商品达到10个或以上时提供7%折扣
 public double discount(Order order)
 {
  ...
 }
}

可以看到,首先要有一个接口(Promotion),然后各个策略去实现这个接口。然而,Python 语言没有 interface 关键字,就是说,Python 里没有像 java、C# 一样的接口。

在策略模式一文的实现中,使用了抽象基类(Abstract Base Class,ABC)来实现接口,这主要是为了写法上看起来和 java、C# 等语言更加的像,易于有这些语言基础的同学理解和对比。

抽象基类是在 Python 语言诞生 15 年后,Python 2.6 才引入的。这里我们不详细介绍抽象基类,因为即便现在也很少有代码使用抽象基类。对于多态,Python 有更好的实现方式——鸭子类型(duck typing)。

协议和鸭子类型

所谓 鸭子类型 就是:如果一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么它就是鸭子。这个概念的名字来源于 James Whitcomb Riley 提出的鸭子测试。

初次看到这个描述的小伙伴一定一头雾水,为了理解鸭子类型,我们不得不提到另一个名词——协议。

在面向对象编程中,协议是非正式的接口,是一组方法,只由文档和约定定义,因此,协议不能像正式接口那样施加强制性约束。而 Python 的哲学就是尽量支持基本协议。

翻译成人话,就是:Python 中没有接口,在需要使用接口的地方,就用协议代替。所谓协议,其实就是一组方法,和接口中定义的方法一个意思。只不过协议是不是强制性的约定,如果你不遵守协议,那么也没关系,运行时报错就是了。

这样就好理解鸭子类型了,“如果一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子” 这就表示已经遵守了协议,“那么它就是鸭子”,意味着你可以在其他用到“鸭子”的地方,用“这只鸟”替换。这不就是多态吗?

用“鸭子类型”来实现策略模式也很简单,删掉抽象基类就可以了。(这就是为什么抽象基类很少使用的原因,因为删掉代码也一样正确啊。)有兴趣的小伙伴可以自己尝试一下代码。

Python 中的协议举例

Python 中有很多的协议,比如迭代器协议,任何实现了 __iter__ 和 __next__ 方法的对象都可称之为迭代器,但对象本身是什么类型不受限制,这得益于鸭子类型。

from collections import Iterable
from collections import Iterator

class MyIterator:
 def __iter__(self):
  pass
 def __next__(self):
  pass
print(isinstance(MyIterator(), Iterable)) 
print(isinstance(MyIterator(), Iterator))

输出:

True
True

结语

鸭子类型是编程语言中动态类型语言中的一种设计风格,一个对象的特征不是由父类决定,而是通过对象的方法决定的。

Python 不是不支持多态,而是 Python 本身就是一门多态的语言。

相关文章

浅析Python中将单词首字母大写的capitalize()方法

 capitalize()方法返回字符串的一个副本,只有它的第一个字母大写。对于8位的字符串,这个方法与语言环境相关。 语法 以下是capitalize()方法的语法: s...

python中pytest收集用例规则与运行指定用例详解

前言 上篇文章相信大家已经了解了pytest在cmd下结合各种命令行参数如何运行测试用例,并输出我们想要看到的信息。那么今天会讲解一下pytest是如何收集我们写好的用例?我们又有哪些...

python魔法方法-属性转换和类的表示详解

python魔法方法-属性转换和类的表示详解

类型转换魔法 类型转换魔法其实就是实现了str、int等工厂函数的结果,通常这些函数还有类型转换的功能,下面是一些相关的魔法方法: •__int__(self) •...

python字典的常用操作方法小结

Python字典是另一种可变容器模型(无序),且可存储任意类型对象,如字符串、数字、元组等其他容器模型。本文章主要介绍Python中字典(Dict)的详解操作方法,包含创建、访问、删除、...

栈和队列数据结构的基本概念及其相关的Python实现

先来回顾一下栈和队列的基本概念: 相同点:从"数据结构"的角度看,它们都是线性结构,即数据元素之间的关系相同。 不同点:栈(Stack)是限定只能在表的一端进行插入和删除操作的线性表。...