Python笔记 · 鸭子类型 / Duck Typing

news2024/12/21 19:51:53

1. 问题的由来

我初次意识到鸭子类型在存在是在学习Sklearn时,在《Hands-On Machine Learing》一书的第二章,作者提供了一个自定义的Tansformer,使用自定义Transformer的好处在于:你既可以实现自己需要的数据处理逻辑,又能保证它可以很好地融入到Sklearn的计算框架中,例如让自己的Transformer与其他Transformer一起加入到Pipeline中。在那个名为CombinedAttributesAdder的Transformer中,它继承了BaseEstimator和TransformerMixin两个基类,并提供了两个方法:fit和transform:

# author: https://laurence.blog.csdn.net/
class CombinedAttributesAdder(BaseEstimator, TransformerMixin):
    ...
    def fit(self, X, y=None):
        ...
    def transform(self, X):
        ...

作为一个静态语言出身刚刚开始学习Python不久的程序员,我的第一直觉是:fit和transform一定是重写或实现了BaseEstimator和TransformerMixin两个父类约定的方法。当我好奇地点开两个类浏览它们的代码时,让我意外且困惑的是:BaseEstimator并没有定义fit方法,TransformerMixin也没有定义transform方法。本文地址:https://laurence.blog.csdn.net/article/details/128798589,转载请注明出处!

Sklearn的官方文档:function :: fit 与 function :: transform 均明确指出:所有的Estimator都应提供fit方法,所有的Transformer都应提供transform方法;检索Sklearn中大量的Estimator和Transformer均实现了这些约定的方法并在适当的时机(例如在Pipeline里)被调用过,然而,它们却都没有定义在相应的基类中。这让人非常困惑,一定是哪里出现了知识漏洞,造成了这一“无法解释”的状况。

2. 鸭子类型

人们总是用那句人尽皆知的短语解释鸭子类型:

如果一只鸟走起来像鸭子,叫起来像鸭子,游起泳来也像鸭子,那它就是鸭子。

或者,下面这张图片更能体现鸭子类型的精髓:

请添加图片描述

这些比喻确实揭示了鸭子类型的实质,但真切地理解它还是要通过代码。我们不打算使用Duck,Dog一类示例代码了,借用 https://devopedia.org/duck-typing一文列举的短语检索的示例显得更有实际意义:

import random
import string
# author: https://laurence.blog.csdn.net/
class Book:
    def __init__(self, num_pages):
        self.num_pages = num_pages

    def get_page(self, number):
        return f"[ Book ] text of page {number}: {''.join(random.sample(string.ascii_letters, 20))}"

def search_phrase(book, phrase):
    i = 1
    while i <= book.num_pages:
        text = book.get_page(i)
        print(text)
        if phrase in text:
            print(f">>> Found phrase \"{phrase}\" in page {i}")
            return True
        else:
            print(f">>> Not found phrase \"{phrase}\" in page {i}")
        i+=1

    return False

book = Book(2)
search_phrase(book, "duck typing")

程序输出:

[ Book ] text of page 1: vUdNGYhupVTCsIKjQLOS
>>> Not found phrase "duck typing" in page 1
[ Book ] text of page 2: pJPGXWAboHeIuVTvatwd
>>> Not found phrase "duck typing" in page 2

上面的代码中,我们定义了一个Book类和一个短语检索函数search_phrase(),在短语检索函数的实现中,我们要使用到Book类的num_pages属性和get_page()方法,以便完成书本内容的遍历工作。在当前这个阶段,search_phrase()看上去就是为Book类专门设计的一样,它工作良好,也没有歧义。现在,到了揭示Python语言动态性的时候了,我们添加一个新的类Newspaper,并试图让search_phrase()检索它里面的内容:

# author: https://laurence.blog.csdn.net/
class Newspaper:
    def __init__(self, num_pages):
        self.num_pages = num_pages
    def get_page(self,number):
        return f"[ Newspaper ] text of page {number}: {''.join(random.sample(string.ascii_letters, 20))}"

newspaper = Newspaper(2)
search_phrase(newspaper, "duck typing")

程序输出:

[ Newspaper ] text of page 1: TFyQnOBctNWmfMjxKRew
>>> Not found phrase "duck typing" in page 1
[ Newspaper ] text of page 2: ndhGolAsZuTPXNRfQceF
>>> Not found phrase "duck typing" in page 2

你会发现,Newspaper搭配search_phrase()也可以工作,没有报任何错误,Newspaper就是“鸭子类型”,它满足search_phrase()对Book这个“概念”的所有要求:有num_pages属性和get_page()方法,所以它就是一本“书”。

3. 关于“动态性”的思考

上述鸭子类型示例得以运行通过且没有报错的原因在于:Python对传入search_phrase()函数的book参数类型没有进行类型检查!但这只是一个表面化的理解,再跟进一步思考就会意识到这个说法并不准确。首先,Python只是在编译期“不做类型检查”,在运行阶段依然会检查数据类型,所以这只能解释在编写这段代码时IDE没有报错,不能解释代码执行时为什么没有报错,进而我们就该意识到,既然程序能成功运行,就说明鸭子类型的示例代码能够通过Python的(运行期)类型检查,即:Python的解释器认为(对于search_phrase()函数来说)book和newspaper就是同一“类型”

那Python解释器为什么会得出这样的结论呢?唯一经得起推敲的解释就是:不同于静态类型语言,Python同时作为动态类型语言和动态语言,根本无法确定和控制book参数的实际类型,因为:

  • Python的“动态类型语言”特性决定了:Python的变量在其生命周期内可以改变自己的类型。例如:先a=‘xyz’,后a=12.3,a从str类型编程float类型,这看似简单,但在静态类型语言中是做不到的,a不是单纯的被赋予了新的值,而是连类型也改变了;
  • Python的“动态语言”特性决定了:Python的变量(特指复杂数据结构:类和对象)在其生命周期内可以改变自己的结构,通过动态添加或删除属性和方法,一个类型A可以改成和类型B或者C完全一样,当然,也可以被改的“面目全非”

上述两点明确地告诉我们:在Python中,由于它的“动态”特性,导致变量的类型随时可以变化,在这样的前提下,如果你是Python解释器的设计者,要怎么进行“类型检查”呢?你只能让Python解释器放宽“类型检查”的条件:只要在当前的上下文中(例如一个函数体内),调用方对这个类型所期望的属性和方法它都有,那它就是那个“正确的”类型。

这是在动态类型语言环境下,人们可以给出的最合理的类型检查方案了,因为类型动态化以后势必要比静态类型丢失很多类型相关的信息,使得动态类型语言的类型检查无法做到像静态类型语言那样安全而严格;但也正因为如此,才赋予了动态类型语言极大的灵活性,鸭子类型就是一个典型的例子,你几乎无法在静态类型语言中看到鸭子类型,它们根本无法通过编译的。所以说:这是一把双刃剑,静态类型语言拥有安全有效的类型检查,在代码编写期间就能发现大量的编码错误,同时借助IDE还能有效实现代码提示和自动补全,代价就是要在编写代码时附带大量类型声明,而动态类型语言在上述两方面都很弱,更多的是靠程序员自己,但是它的灵活性确实在很多场景下提升了编程效率。

伴随着这个问题的解答,也化解了我此前的一个疑问:为什么在IDE里Python的代码自动补全和参数列表提示做的那么“差”,本质原因也是因为Python是动态类型语言,IDE无法像Java那样在Python代码中获得足够的类型信息用于支持代码提示和补全操作。

4. 回答问题

最后,回到文章开始提出的问题:为什么CombinedAttributesAdder必须实现fit和transform方法,但又没有定义在BaseEstimator和TransformerMixin中?

fit和transform方法分别代表Estimator和Transformer,而Sklearn中其实并没有Estimator和Transformer这两个(抽象)基类,实际上,它们就是鸭子类型一直强调的“属于文档”的那一部分:即:由于鸭子类型在代码层面上并有任何实体特征(比如像其他语言中的接口或抽象基类),只是靠开发者“人为”遵守约定的方法或属性(例如本例中的fit和transform方法),因此,文档和注释就显得尤为重要了,也就是说:开发者要通过文档和注释告知用户:系统中存在Estimator和Transformer这两个“概念”,它们都是鸭子类型,没有相关的类来指代这两个“概念”,但是,如果你们想要开发它们的具体类,你必须得实现fit和transform方法,否则在程序运行时,其他的代码会调用到这两个方法,如果你没有实现它们,你的程序就会报错。

如果换做是其他语言,鸭子类型大概率会使用接口(Interface)进行定义,在Python这种动态语言里,鸭子类型就是一种“纯纯的口头约定”,但你不实现的话,在调用到具体方法时就会报错,为了告知用户你的程序里有这么个“约定”,你只能在文档和注释中告诉它们

到这里,我们可以说疑问解答了“一半”,那就是:在Sklearn中,Estimator和Transformer是明确无误的重要“接口”(非编程语言上的Interface,就是一个“概念”,代表一组约束),但是Sklearn选择将它们处理为“鸭子类型”,所以在代码层面上就没任何“对应物”了,并在文档中告知了用户。问题的“另一半”在于,既然有BaseEstimator和TransformerMixin这两个基类,为什么不在这两个类上将fit和transform添加为抽象方法,而约束所有子类去实现它们呢?对于这个问题,以我目前对Sklearn和Python的理解,还不能给出非常确信的解释,但它这个和Python编程风格以及支持抽象基类的时间有关,在《流畅的Python》一书中曾经这样说到:Python语言在诞生15年之后,才在2.6版本中引入了抽象基类,且即使是现在也很少有代码使用抽象基类。这是一个值得思考的问题,显然在Python中是有实现类似抽象基类功能的机制,这个机制其实就是鸭子类型,或者说:是由于鸭子类型,而不怎么需要抽象基类了。本文地址:https://laurence.blog.csdn.net/article/details/128798589,转载请注明出处!

备注提示:

动态类型语言和动态语言是完全不同的两个概念。动态类型语言是指在运行期间才去做数据类型检查的语言,说的是数据类型,动态语言说的是运行时改变结构,说的是代码结构。动态类型语言的数据类型不是在编译阶段决定的,而是把类型绑定延后到了运行阶段。Python既是动态语言双是动态类型语言。

参考资料:

Python 类型系统与类型检查(翻译)

https://cloud.tencent.com/developer/article/1484390

https://devopedia.org/duck-typing

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

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

相关文章

【应用】SpringCloud -- Gateway

SpringCloud -- GatewayGateway 网关概述Gateway 的功能Gateway 核心概念Gateway 网关搭建Gateway 的配置及使用predicate 断言filter 过滤器GlobalFilter 全局过滤器Gateway 的几点问题过滤器执行顺序跨域问题Gateway 网关概述 Gateway 的功能 在微服务当中&#xff0c;有很…

PG15 pg_basebackup 代码解析

背景 先前 PG 版本 pg_basebackup 的代码较为复杂&#xff0c;pg_basebackup 在备份过程中做了很多事情&#xff0c;但这部分代码逻辑没有完全解耦&#xff0c;导致一个文件里包含了很多功能的逻辑&#xff0c;影响了代码的可读性和可修改性。 因此&#xff0c;PG 15 针对这部…

模电学习3. 差模、共模干扰与安规电容

模电学习3. 差模、共模干扰与安规电容一、简介1. 安规2. 电源安规标准二、差模干扰与共模干扰1. 共模干扰2. 差模干扰&#xff08;1&#xff09;定义&#xff08;2&#xff09;来源与特性3. 测量4. 消除电源线路中的差模干扰&#xff08;1&#xff09;差模电容5. 消除电源线路中…

TOOM加强网络舆情监控制定处置预案,抓好舆情监控管理?

舆情预案是指根据对未来舆情发展趋势的预测&#xff0c;制定预先准备的舆情处置方案&#xff0c;以应对可能出现的舆情危机。舆情预案包括舆情预警体系、舆情应对策略、应对措施等内容&#xff0c;旨在在舆情危机发生前进行有效的预防和准备&#xff0c;避免舆情危机扩大&#…

95. BERT预训练数据代码

为了预训练之前实现的BERT模型&#xff0c;我们需要以理想的格式生成数据集&#xff0c;以便于两个预训练任务&#xff1a;遮蔽语言模型和下一句预测。一方面&#xff0c;最初的BERT模型是在两个庞大的图书语料库和英语维基百科的合集上预训练的&#xff0c;但它很难吸引这本书…

Zookeeper 教程

Zookeeper 教程Zookeeper 概述分布式应用Zookeeper 架构图ZooKeeper当中的主从与主备&#xff1a;Zookeeper的特性分布式应用的优点分布式应用的挑战什么是Apache ZooKeeper&#xff1f;ZooKeeper的好处Zookeeper 基础ZooKeeper的架构层次命名空间Znode的类型Sessions&#xff…

九龙证券|外资放大招,400亿巨头狂飙!这只翻倍股却突然崩了

昨日超级“开门红”之后&#xff0c;今天上午港股又有多只重磅个股大幅涨超10%。 今日上午&#xff0c;港股整体窄幅震荡&#xff0c;上午收盘&#xff0c;恒生指数微跌0.05%&#xff0c;恒生科技指数涨0.22%。 职业板块方面&#xff0c;媒体、软件服务、电信等涨幅居前&#…

【BLE】ANCS(Apple Notification Center Service)

目录1. 前言1.1 名词解释1.2 ANCS概述2. ANCS的特征2.1 通知源2.2 控制点和数据源2.3 获取通知属性2.4 获取应用属性2.5 执行通知操作2.6 通知操作3. 更多详情参考1. 前言 1.1 名词解释 NP(Notification Provider)&#xff1a;消息提供者&#xff0c;指的是ANCS服务的生产者&…

【JavaSE】入门概述(1~41)

1.Java视频及配套资料下载指南 2.Java基础学习导读 Java语言的三个层面 基本语法&#xff1a;变量、运算符、流程控制、数组面向对象&#xff1a;把数据及对数据的操作方法放在一起&#xff0c;作为一个相互依存的整体——对象高级应用&#xff1a;多线程、集合、IO流、网络…

性能怪兽-Nginx常用配置指北

目录 安装 Nginx操作命令 https反向代理 负载均衡 轮询 加权轮询 最少连接 加权最少连接 IP Hash 普通 Hash 动静分离 资源压缩 缓冲区 缓存机制 解决跨域 防盗链设计 配置SLL证书 性能优化 打开长连接配置 开启零拷贝技术 开启无延迟或多包共发机制 调整W…

Python连接Liunx中mysql数据库-增删改查

上一篇文章已经讲解了如何连接liunx中的mysql数据库&#xff0c;如果没有连接好数据库的话&#xff0c;可以看这一篇文章 增删改查Mysql中查询操作1.创建游标2.定义一个sql的查询语句3.调用游标内的sql语句执行操作4.打印出查询结果5.完整代码6.指定查询Mysql中新增操作1.单条数…

jupyter 常用记录

安装windows环境下 运行cmd 然后&#xff1a;在命令提示窗口输入pip install jupyter,然后回车&#xff1b;完成后运行 jupyter notebook 前言 提起jupyter notebook&#xff0c;应该很多学习过Python的同学都不陌生。虽然用jupyter notebook的同学相对较少&#xff0c;但是提…

Java多级缓存是为了解决什么的?

前言 提到缓存&#xff0c;想必每一位软件工程师都不陌生&#xff0c;它是目前架构设计中提高性能最直接的方式。 缓存技术存在于应用场景的方方面面。从网站提高性能的角度分析&#xff0c;缓存可以放在浏览器&#xff0c;可以放在反向代理服务器&#xff0c;还可以放在应用…

使用JINJA2模板部署自定义文件(RH294)

在ansible中有许多模板可以用于修改现有的文件比如—— lineinfile和blockinfile但是&#xff01;我们还有一种更加便捷而且牛皮的方法为其构建模板在部署该文件是自动为受管主机自定义次模板配置文件而这些模板中非常有名的就是 JINJA2ansible将Jinja2模板系统用于模板文件 并…

Java设计模式-职责链模式Chain of Responsibility

介绍 职责链模式&#xff08;Chain of Responsibility Pattern&#xff09;, 又叫 责任链模式&#xff0c;为请求创建了一个接收者对象的链(简单示意图)。这种模式对请求的发送者和接收者进行解耦。职责链模式通常每个接收者都包含对另一个接收者的引用。如果一个对象不能处理…

C语言#include的用法详解

#include叫做文件包含命令&#xff0c;用来引入对应的头文件&#xff08;.h文件&#xff09;。#include 也是C语言预处理命令的一种。#include 的处理过程很简单&#xff0c;就是将头文件的内容插入到该命令所在的位置&#xff0c;从而把头文件和当前源文件连接成一个源文件&am…

Oracle数据库故障处理-存储单块读hang分析处理

1 故障描述 2023年1月27日下午接到业务反馈数据库存在大量的锁表阻塞信息&#xff0c;并且业务的页面以及数据库的一些查询均处于阻塞状态&#xff0c;简单的查询sql也需要查询很长时间且未返回结果,数据库hang状态。 2 故障原因分析 2.1 数据库层面分析 接到业务反馈后&am…

如果放在几年前,无法想到互联网会蜕变成今天这样一副模样

如果放在几年前&#xff0c;你是万万无法想到互联网会蜕变成今天这样一副模样。尽管如此&#xff0c;这样一种蜕变却在真实地发生着。不知道你有没有发现就连前两年火爆的短视频人们都懒得刷了。所有的这一切都在告诉我们&#xff0c;互联网正在发生一场深刻而又彻底的嬗变。如…

SQL Server数据库版本总结

一、为什么要写这篇文章 之所以专门写一篇文章来整理归纳SQL Server各个版本的功能区别&#xff0c;是因为遇到过两次真实的客户案例&#xff0c;因为数据库版本选取不当&#xff0c;导致生产系统宕机的情况。 案例一 某客户安装了 32位 版本的SQL Server 2008 R2 数据库&…

人工智能轨道交通行业周刊-第31期(2023.1.16-1.29)

本期关键词&#xff1a;磁悬浮原理、小米石、通信铁塔维护、桥隧工巡检、地方铁路十大新闻 1 整理涉及公众号名单 1.1 行业类 RT轨道交通中关村轨道交通产业服务平台人民铁道世界轨道交通资讯网铁路信号技术交流北京铁路轨道交通网上榜铁路视点ITS World轨道交通联盟VSTR铁路…