python:鸭子类型使用场景

python:鸭子类型使用场景

1 前言

“一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟可以被称为鸭子。“----鸭子模型

鸭子模型是Python中的一种编程哲学,也被称为“鸭子类型”。它来源于一句话:“如果它走起路来像鸭子,叫起来也像鸭子,那么它就是鸭子。”这个哲学思想强调对象的行为比其具体类型更重要。与C++、Java等编译型语言不一样的是,Python作为解释器语言,其语言层面的设计理念有独特之处,鸭子模型便是其中之一。在面向对象的世界中,编译型语言判断一个对象是否隶属于某个类,依靠的是类的继承机制,换句话说,即使一个对象实现了某个类的所有方法也不行;而在Python中,只要实例对象实现了某个类的所有必要的方法,即使不存在继承关系,也可以看作是这个类。

2 使用

举个简单的栗子:

class Duck:def walk(self):print(f'I am duck, walk with {self}')def drink(self):print(f'I am duck, drink with {self}')class SmallBird:def walk(self):print(f'I am also duck, walk with {self}')def drink(self):print(f'I am also duck, drink with {self}')def duck_action_run(obj):obj.walk()obj.drink()duck = Duck()
smallBird = SmallBird()
duck_action_run(duck)print('*' * 10)
duck_action_run(smallBird)

结果:

I am duck, walk with <__main__.Duck object at 0x0000017442006FA0>
I am duck, drink with <__main__.Duck object at 0x0000017442006FA0>
**********
I am also duck, walk with <__main__.SmallBird object at 0x0000017442006FD0>
I am also duck, drink with <__main__.SmallBird object at 0x0000017442006FD0>

在这里插入图片描述

我们来分析下这个简单示例,首先定义了Duck类和SmallBird类,分别具有walk方法和drink方法,两者并不具备任何继承关系;然后定义一个duck_action_run函数,该函数接收一个对象,在函数内调用该对象的walk方法和drink方法;最后实例化一个Duck和SmallBird对象,先后传递给duck_action_run函数来执行,并都可以执行成功。在这个例子里,我们可以引入一个"协议"的概念,"协议"代表一系列特征,譬如鸭子协议是walk和drink,任何实现了鸭子协议的对象都可以当作鸭子,这个就是鸭子模型和协议。

(1)再举个Python中更加常见的案例,with读取以及关闭文件资源:

“with”,上下文管理器。众所周知,在任何编程语言里面,文件处理都是基本的IO操作,都包含open和close两个操作,因为这两个操作是操作系统要求的。在操作系统里,每当打开一个文件,获取一个文件句柄后就会占用操作系统的资源,所以操作系统要求文件处理完后需要应用去close,释放掉文件句柄资源。然而现实情况是,开发者往往忘记close,最终导致资源泄漏问题频发。为了解决这个问题,Python使用with来进行上下文管理,在with的语句块结束后自动close,不再需要手动操作(with关键字会自动处理完后关闭文件资源,释放资源占用)。上下文管理器示例如下:

with open("xiaoxu.txt", 'r', encoding='utf-8') as f:f.read()

在上面这个例子中,在with语句中进行open操作,然后调用read方法,最后并没有显示调用close,但不存在资源释放问题,因此with结束后会自动调用了close。那么with的底层原理是什么?类比鸭子模型介绍中关于协议的概念:

协议(Protocol)则是一种约定或契约,描述了对象应该具有的方法和属性。在Python中,协议是一种非正式的接口定义方式,它没有严格的语法要求,只需确保对象实现了协议中定义的方法和属性即可。协议允许我们根据对象的行为来定义接口,而不依赖于具体的类或类型。使用协议,我们可以通过定义一个适当的接口来描述对象的行为,而不仅仅依赖于继承关系。这样,不同的对象可以来自不同的类,但只要它们实现了相同的协议,我们就可以在代码中使用它们。
Python中的一些常见协议包括可迭代协议(Iterable Protocol)、可调用协议(Callable Protocol)和上下文管理器协议(Context Manager Protocol)等。这些协议定义了一组方法或属性,用于描述对象应该具备的行为。

我们回到刚才的示例中,在with中对应的是实现上下文管理器协议__enter__方法和__exit__方法,换句话说,只要一个类具备__enter__方法和__exit__方法,就可以使用with管理,with开始时调用__enter__方法,结束时自动调用__exit__方法,示例代码如下:

class OwnFile:def run(self):passdef __enter__(self):print("开始读取")return selfdef __exit__(self, exc_type, exc_val, exc_tb):print(f'exc_type:{exc_type}, exc_val:{exc_val}, exc_tb:{exc_tb}')print('结束读取')with OwnFile() as f:print(f)

结果:

开始读取
<__main__.OwnFile object at 0x000001E49570B2B0>
exc_type:None, exc_val:None, exc_tb:None
结束读取

在这里插入图片描述

在这个例子中,我们可以看到没有显示调用OwnFile的__enter__方法和__exit__方法,由with自动调用了,因此对于资源释放类,资源释放的操作可以放到__exit__方法中,这样配合with语句使用会方便很多,也降低出错的概率。

(2)另外以可迭代对象和迭代器为例:

可迭代 (iterable):如果一个对象具备有__iter__() 或者 __getitem__()其中任何一个魔术方法的话,这个对象就可以称为是可迭代的。其中,__iter__()的作用是可以让for循环遍历,而__getitem__()方法可以让实例对象通过[index]索引的方式去访问实例中的元素。所以,列表List、元组Tuple、字典Dictionary、字符串String等数据类型都是可迭代的。

迭代器 (iterator): 如果一个对象同时有__iter__()和__next__()魔术方法的话,这个对象就可以称为是迭代器。__iter__()的作用前面我们也提到过,是可以让for循环遍历。而__next__()方法是让对象可以通过 next(实例对象) 的方式访问下一个元素。列表List、元组Tuple、字典Dictionary、字符串String等数据类型虽然是可迭代的,但都不是迭代器,因为他们都没有next( )方法。

如何判断可迭代(iterable) & 迭代器(iterator)

我们可以借助Python中的**isinstance(object, classinfo)**函数来判断一个对象是否是一个已知类型。如下例子中,通过isinstance( )函数分别判断列表、元组、字典、字符串是不是可迭代或迭代器。

from collections import Iterable
from collections import Iteratorprint(f"List is 'Iterable': {isinstance([], Iterable)}")
print(f"Tuple is 'Iterable': {isinstance((), Iterable)}")
print(f"Dict is 'Iterable': {isinstance({}, Iterable)}")
print(f"String is 'Iterable': {isinstance('', Iterable)}")print("=" * 25)print(f"List is 'Iterator': {isinstance([], Iterator)}")
print(f"Tuple is 'Iterator': {isinstance((), Iterator)}")
print(f"Dict is 'Iterator': {isinstance({}, Iterator)}")
print(f"String is 'Iterator': {isinstance('', Iterator)}")# 输出如下:
# List is 'Iterable': True
# Tuple is 'Iterable': True
# Dict is 'Iterable': True
# String is 'Iterable': True
# =========================
# List is 'Iterator': False
# Tuple is 'Iterator': False
# Dict is 'Iterator': False
# String is 'Iterator': False

通过对定义的分析和比较我们得知:迭代器都是可迭代的(因为迭代器都包含__iter__()函数),但可迭代的不一定是迭代器(因为未必每个可迭代就包含__next__()方法)。

创建一个迭代器

得益于Python的鸭子类型特性,只要我们实现类似具备某一特征的方法,就可以认为它就是什么。所以我们定义了一个类并在类中实现__iter__()和__next__()方法,那么这个类就可以当做是一个迭代器了。

from collections import Iteratorclass Data:def __init__(self, x):self.x = xdef __iter__(self):return selfdef __next__(self):if self.x >= 10:raise StopIterationelse:self.x += 1return self.xdata = Data(0)print(f"data is 'Iterator': {isinstance(data, Iterator)}")# 输出如下:
# data is 'Iterator': True

如上例子中我们可以看到,最后我们用isinstance()函数判断得到结果为True,证明我们定义的实例对象是一个真正的迭代器了。因为是迭代器,我们就可以用for循环来验证试试。

class Data:def __init__(self, x):self.x = xdef __iter__(self):return selfdef __next__(self):if self.x >= 10:raise StopIterationelse:self.x += 1return self.xdata = Data(0)for d in data:print(d)# 输出如下:
# 1
# 2
# 3
# 4
# 5
# 6
# 7
# 8
# 9
# 10

执行结果:

在这里插入图片描述

上述例子中,我们定义的类对象内部,把x的值显示在大于等于10以内,否则就会抛出StopIteration异常错误(当然实际使用时并不会出错,只是中断继续执行。)我们先创建了一个初始值为0的实例对象,最后顺利的用for循环遍历了从1到10的数字,因为内部对大于等于10的限制,所以输出到10的时候就停止了。特别要注意的是,如果你再次单独去执行for循环的话不会有任何输出,因为迭代器默认只运行一次。

除了自己定义__iter__()和__next__()魔术方法的外,我们还可以使用Python内置的iter()函数来返回一个迭代器,像下面这样。iter()方法,可以传入可迭代对象(可迭代对象有__iter__()和__getitem__()魔术方法)参数,返回迭代器对象(迭代器对象有__iter__()和__next__()魔术方法)。

from collections.abc import Iteratorlist_a = [1, 2, 3, 4, 5, 6]
my_iterator = iter(list_a)print(f"my_iterator is 'Iterator': {isinstance(my_iterator, Iterator)}")# 输出如下:
# my_iterator is 'Iterator': True

我们知道,迭代器必须具备两个基本方法__iter__()和__next__(),而__next__()方法是让对象可以通过 next(实例对象) 的方式访问下一个元素。所以让我们验证下用next()的方式去访问这个我们转换过的迭代器是否能正常运行。

from collections.abc import Iteratorlist_a = [1, 2]
my_iterator = iter(list_a)print(f"my_iterator is 'Iterator': {isinstance(my_iterator, Iterator)}")# 输出如下:
# my_iterator is 'Iterator': Trueprint(next(my_iterator))
print(next(my_iterator))
print(next(my_iterator))  # error:StopIteration
# 输出如下:
# 1
# 2
# StopIteration

最后,我们还可以使用Python内置的dir()函数来看看传入参数的属性,方法等信息,比如我们用它来看看之前从list转换成的my_iterator迭代器。

print(dir(my_iterator))

结果:

['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', 
'__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', 
'__iter__', '__le__', '__length_hint__', '__lt__', '__ne__', '__new__', '__next__', 
'__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', 
'__str__', '__subclasshook__']

可以发现,也是意料之中的,my_iterator迭代器包含了两个基本方法__iter__()和__next__()方法。

(3)总结一下

在Python中,鸭子模型指的是我们关注对象的行为(方法和属性)而不是其具体的类型。如果一个对象具有我们所期望的行为,我们就可以将其视为满足我们的需求,而无需关注其实际的类型。这种灵活性使得在Python中编写可重用和灵活的代码变得更加容易。例如,如果我们编写了一个需要迭代对象的函数,我们只关心对象是否具有__iter__()方法,而不关心它是否是一个具体的列表、元组或集合。

总之,鸭子模型让我们专注于对象的行为而不是其具体类型,在编写灵活、可重用的代码时非常有用。

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

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

相关文章

qt 5.15.x 安装android过程记录

1.经过好几天的qt for android 安装&#xff0c;发现存在很多坑 参考其他文章可以编译出APK文件。但是我发现(我的机器上)无法调试apk程序&#xff0c;不能调试那怎么行呢&#xff0c;看了很多文章都是运行出结果了就结束了。没有展示怎么调试程序。 很多文章都是建议安装JDK8…

CTF数据安全大赛crypto题目解题过程

CTF-Crypto加密题目内容 下面是一个Base64加密的密文 bXNobnszODdoajM3MzM1NzExMzQxMmo4MGg0bDVoMDYzNDQzNH0原文链接&#xff1a; 数据安全大赛CTF-Crypto题目 - 红客网-网络安全与渗透技术 我们用Python写一个解密脚本&#xff1a; import base64 import time #base64加密…

韩顺平0基础学Java——第7天

p110-p154 控制结构&#xff08;第四章&#xff09; 多分支 if-elseif-else import java.util.Scanner; public class day7{public static void main(String[] args) {Scanner myscanner new Scanner(System.in);System.out.println("input your score?");int s…

什么是Jetpack

Jetpack Jetpack 是一套组件库、工具&#xff0c;可帮助开发人员遵循最佳做法&#xff0c;减少样板代码并编写可在 Android 版本和设备上一致工作的代码&#xff0c;以便开发人员可以专注于他们关心的代码 组成 主要包含四部分&#xff1a;架构&#xff08;Architecture&…

Linux:进程通信(三)信号的捕捉

目录 一、信号捕捉函数 1、signal函数 2、sigaction函数 二、用户态与内核态 1、用户态 2、内核态 用户态与内核态转换 三、volatile关键字 四、SIGCHLD信号 一、信号捕捉函数 1、signal函数 signal函数是C语言标准库中的一个函数&#xff0c;用于处理Unix/Linux系…

Ps 滤镜:其它

Ps菜单&#xff1a;滤镜/其它 Filter/others “其它”子菜单中的滤镜允许创建自己的滤镜、使用滤镜修改蒙版、在图像中使选区发生位移和快速调整颜色。 HSB/HSL HSB/HSL 主要用于实现 RGB、HSB 及 HSL 三种模型的相互转换。 比如&#xff0c;当执行本滤镜从 RGB 转换为 HSB 之后…

YOLOv8网络结构介绍

将按照YOLOv8目标检测任务、实例分割任务、关键点检测任务以及旋转目标检测任务的顺序来介绍&#xff0c;主要内容也是在目标检测任务中介绍&#xff0c;其他任务也只是Head层不相同。 1.YOLOv8_det网络结构 首先&#xff0c;YOLOv8网络分成了三部分&#xff0c;分别是主干网络…

接口信息解析

在进行浏览器网站的接口测试时&#xff0c;需要解析以下关键信息以确保接口的正确性和性能&#xff1a; 1. 接口地址&#xff08;URL&#xff09;&#xff1a; 接口的地址是测试的基础&#xff0c;包括接口的协议&#xff08;如 HTTP 或 HTTPS&#xff09;、主机名、端口&…

自动控制原理学习--平衡小车的控制算法(三)

上一节PID的simulin仿真&#xff0c;这一节用LQR 一、模型 二、LQR LQR属于现代控制理论的一个很重要的点&#xff0c;这里推荐B站的【Advanced控制理论】课程&#xff08;up主DR_CAN&#xff09;&#xff0c;讲得很好&#xff0c;这里引用了他视频里讲LQR的ppt。 LQR属于lo…

(三)小程序样式和组件

视频链接&#xff1a;尚硅谷2024最新版微信小程序 文章目录 小程序的样式和组件介绍样式-尺寸单位 rpx样式-全局样式和局部样式组件-组件案例演示组件案例-轮播图区域绘制组件案例-轮播图图片添加组件案例-绘制公司信息区域组件案例-商品导航区域组件案例-跳转到商品列表组件案…

python爬取sci论文等一系列网站---通用教程超详细教程

环境准备 确保安装了Python以及requests和BeautifulSoup库。 pip install requests beautifulsoup4确定爬取目标 选择一个含有SCI论文的网站&#xff0c;了解该网站的内容布局和数据结构。 &#xff08;1&#xff09;在浏览器中访问目标网站&#xff0c;右键点击页面并选择…

案例研究|硬之城借助DataEase以数据驱动供应链精细化管理

深圳硬之城信息技术有限公司&#xff08;以下简称为“硬之城”&#xff09;成立于2015年&#xff0c;专注电子元件供应链领域&#xff0c;定位于电子产业供应链与智造平台。硬之城通过名为“Allchips”的集成式服务平台&#xff0c;为客户提供一站式的电子元件采购和供应链管理…

VTK 建模方法:建模基础

VTK 建模方法&#xff1a;建模基础 VTK 建模方法&#xff1a;建模基础VTK 中模型的表达实例1&#xff1a;自定义 vtkPolyData实例2&#xff1a;vtkTubeFilter实例3&#xff1a;vtkImplicitModeller实例4&#xff1a;vtkRegularPolygonSource实例5&#xff1a;vtkWarpTo VTK 建模…

如何在mac电脑安装 Android SDK

1、在 Mac 电脑上安装 Android SDK 的步骤如下: 前往 Android 开发者网站下载 Android SDK 打开 Android 开发者网站 (https://developer.android.com/studio) 打开下载好的 Android SDK 安装包 2、解压 Android SDK 安装包 打开下载好的 Android SDK 安装包 将 android-…

深度主动学习(Deep Active Learning)——基于pytorch和ALipy工具包实现双向GRU模型

前言 在ALipy的官网说ALipy只支持sklearn和tensorflow模型&#xff0c;模型对象应符合 scikit-learn api。 但是alipy提供了ToolBox的工具箱&#xff0c;里面包装了多种查询策略&#xff0c;计算指标等工具&#xff0c;几乎具有Alipy的全部功能&#xff0c;虽然不能使用ALipy提…

Pycharm2024版,更换安装源

1、选择Python Packages 2、点击图中的小齿轮 3、点击 号 4、添加源地址 常用源如下&#xff1a; 清华&#xff1a;https://pypi.tuna.tsinghua.edu.cn/simple 阿里云&#xff1a;http://mirrors.aliyun.com/pypi/simple/ 中国科技大学 https://pypi.mirrors.ustc.edu.cn…

HTML5 Canvas发光Loading动画特效源码

源码介绍 之前我们分享过很多基于CSS3的Loading动画效果&#xff0c;相信大家都很喜欢。今天我们要来分享一款基于HTML5 Canvas的发光Loading加载动画特效。Loading旋转图标是在canvas画布上绘制的&#xff0c;整个loading动画是发光3D的视觉效果&#xff0c;HTML5非常强大。 …

索引失效情况

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;面经 ⛺️稳中求进&#xff0c;晒太阳 一、索引列上运算操作。 不要在索引列上进行运算操作&#xff0c;否则索引会失效。 在tb_user的phone列加上索引&#xff0c;然后进行条件查询&am…

nginx自动部署-跨操作系统

项目里面有一个需求&#xff0c;就是需要用让nginx进程提供给系统管理一个start,stop和getPid方法&#xff0c;这样系统管理可以自动拉起来nginx&#xff0c;达到自动部署的目的。离线部署同样适用 这样一来&#xff0c;我就需要提供windows版本linux不同版本的nginx源码包&am…

解决Vue devtools插件数据变化不会自动刷新

我们使用devtools插件在监测vuex中表单或自定义组件的数据&#xff0c;发现页面数据发生变化后&#xff0c;但是devtools中还是老数据&#xff0c;必须手动点击devtools刷新才能拿到最新的数据。很烦&#xff01; 解决方案&#xff1a; 打开chrome的设置&#xff0c;向下翻&…