Python 中的鸭子类型是什么?

Python 中关于数据类型有不同的哲学。
113 位读者喜欢这篇文章。
Duck mallard swimming in water

Håkon Helberg 在 Unsplash 上拍摄的照片

Python 遵循 EAFP(请求原谅比请求许可更容易)哲学,而不是 LBYL(三思而后行)哲学。Python 的 EAFP 哲学在某种程度上与其“鸭子类型”的编码风格相关联。

当程序员在代码中创建数据时,无论是常量还是变量,一些编程语言需要知道数据的“类型”。 例如,如果您将变量设置为 13,计算机不知道您是想将其用作单词 (“thirteen”) 还是整数(例如 13+12=25 或 13-1=12)。 这就是为什么许多 语言要求程序员在使用数据之前声明数据。

例如,在这段 C++ 代码中,mynumber 变量是整数类型,而 myword 变量是字符串

#include <iostream>
#include <string>

using namespace std;

int mynumber = 13;
string myword = "13";

int main() {
  std::cout << 13+2 << endl;
  std::cout << "My favourite number is " + myword << endl;
}

然而,Python 很聪明,它使用“鸭子测试”:如果一个变量走起来像鸭子,叫起来也像鸭子,那么它就是鸭子。换句话说,应用于计算机科学,这意味着 Python 检查数据以确定其类型。 Python 知道整数用于数学,单词用于交流,因此程序员不必向 Python 解释如何使用在变量中找到的数据。 Python 使用鸭子类型自行判断,并且不会尝试对字符串进行数学运算或打印数组的内容(不进行迭代)等等。

然而,在我们讨论这些概念之前,让我们先回顾一下基础知识

理解“类型”概念的一个类比

“类型化”的概念在编程语言的上下文中经常被讨论,但通常,更深层的含义让我们难以捉摸。所以,让我尝试用一个类比来解释这个概念。

在计算机程序中,对象和其他项目存储在内存中,它们通常由某些“变量名”引用。因此,当您创建特定类的对象(在任何流行的编程语言中)时,您基本上是在为该对象保留一部分内存以供占用,然后您使用该变量名引用此对象。

因此,作为一个类比,您可以将内存中的这个空间想象成一种容器或盒子。 为了便于理解,我们称之为盒子。 所以现在我们有了两样东西——一个对象和一个包含它的盒子。

为了进一步论证,通常,盒子必须被“设计”成能够容纳它所包含的对象(即,用于装火柴的盒子不适合装鞋子,反之亦然,即使物理上是可能的)。 那么,我们可以同意对象和包含它的盒子都必须是相似的类型吗?

实际上,这就是所谓的“静态类型”。 基本上,这意味着不仅对象必须具有“类型”,而且变量名(又名盒子)也必须具有类型,并且它应该是相同或相似的。(稍后我将解释为什么我说“相似”)。 这就是为什么在 Java/C++ 等静态类型语言中,您需要在创建变量时定义变量的类型。 事实上,您可以创建一个类似于盒子的变量名,即使不创建任何对象来放入其中。 您不能在 Python 中这样做。

然而,像 Python 这样的动态类型语言的工作方式不同。 在这里,您可以将变量名想象成“标签”(有点像商店里的价格标签),而不是盒子。 所以,标签没有类型。 相反,如果您询问标签它的类型是什么,它可能会选择它在那一刻标记的对象。 我之所以说“在那一刻”,是因为就像在现实世界中一样,附加到鞋子的标签也可能在不同的时间附加到其他物品上。 因此,Python 解释器本身不会为变量名分配任何类型。 但是,如果您询问变量名它的类型,那么它将为您提供它当前绑定的对象的类型。 这就是动态类型。

这种动态类型与静态类型直接影响您编写代码的方式。 就像在现实世界中你不能把鞋子放在装火柴的盒子里一样,在静态类型语言中也是如此——你通常不能将一种类型的对象放入为另一种类型的对象创建的变量名中。

强类型语言与弱类型语言

这里还有另一个重要的概念需要讨论,即强类型语言和弱类型语言。“类型化”的“强度”与它是动态类型还是静态类型几乎无关。 它更多地与“类型转换”或将一种类型的对象转换为另一种类型的能力有关。 与流行的看法相反,Python 是一种相当强类型的语言,就像 C++ 或 Java 一样。 因此,例如在 Python 中,您不能将“整数”添加到“字符串”,但是,您可以在像 JavaScript 这样的语言中这样做。 事实上,JavaScript 是臭名昭著的“弱类型”语言之一。 因此,应该清楚的是,强/弱类型与静态/动态类型是完全不同的尺度。 一般来说,像 Python 这样的脚本语言倾向于动态类型,而编译语言倾向于静态类型。

鸭子类型、EAFP 和 LBYL

Python 遵循鸭子类型的编码风格。

让我们再次举一个现实世界的例子。 假设你有一个对象“机器 M”。 现在,你不知道这台机器 M 是否有飞行能力。 下图说明了 LBYL 与 EAFP 的处理方式

LBYL vs EAFP

让我们用一些 Python 代码(带有虚构的函数)来阐明这个概念

# LBYL:- Look Before You Leap
if can_fly():
    fly()
else:
    do_something_else()
    
# EAFP:- Easier to Ask Forgiveness than permission
try:
    fly()
except:
    clean_up()  

鸭子类型如何支持 EAFP

鸭子类型非常适合 EAFP 风格的编码。 这是因为我们不关心对象的“类型”; 我们只关心它的“行为”和“能力”。 “行为”基本上是指它的属性,“能力”是指它的方法。

总结

如果你看到很多 if-else 代码块,那么你就是一个 LBYL 编码者。

但是如果你看到很多 try-except 代码块,你可能就是一个 EAFP 编码者。

接下来阅读什么
标签
User profile image.
Anurag Gupta 受过工程师教育,但职业是警察。 他是一位 Python 爱好者,最近与人合著了一本关于 Python 的书,名为《Python 编程:问题解决、包和库》,由 McGraw Hill 出版。 您可以通过 999.anuraggupta 在 Gmail 上联系他,他很乐意听取读者关于 Python 相关主题的意见。

9 条评论

Anurag,这很有趣,我的第一门编程语言是 Pascal,它迫使我养成的良好编码习惯一直保持到现在。 然而,有人后来对我说,对于一种教学语言来说,这实际上是一个弱点,因为它让学生别无选择,而不是让他们明白为什么这些良好的编程习惯比替代方案更可取。 这是一个有效的论点,但我仍然感谢最初的纪律约束。

老实说,如果你看到很多 if/then/else 或很多 try/catch,那可能意味着你是一个糟糕的程序员。

嗯。 或者可能是一个初学者,或者更有可能的是你正在处理一个属性集差异很大的对象输入流。

回复  作者:Alessandro (未验证)

多么精彩的文章! 谢谢你 Anurag。 继 Peter Cheer 在上面的评论之后,我认为如果你再写一篇关于“鸭子类型启用的常见错误以及如何避免它们”的文章,那就太棒了。

我编写 Python 代码已经快 20 年了,我注意到的主要一点是,如果不小心,EAFP 很容易导致非常难以理解的错误消息。

urwid(Python 的 TUI 框架)在这方面深受其害。

(如果你在构建小部件树时没有给它提供它期望的东西,你会得到一个非常难以理解的错误,提示在 urwid 深处的某个地方缺少属性/方法。)

嗯,这向我解释了“鸭子类型”的基本概念。 此外,它还让我清楚地了解了为什么通过类比学习的人似乎永远无法理解他们正在编程的实际机器或他们在操作系统方面遇到的问题。 我现在遇到了一些开发人员,他们无法理解他们被教导的愚蠢类比之外的东西。 现在,他们必须去学习一些关于计算机的现实知识。

恕我直言,有 3 种处理困难概念的方法:(1)跳过它(2)给出一个类比(3)深入研究其内部原理。
现在,根据您所面对的读者,这 3 种方法都很好。
我给出了“变量名”在某些语言(如 C++)中是“盒子”,而在另一些语言(如 Python)中是“标签”的类比,以便让读者更深入地理解这个概念,而无需讨论更深层次的细节。 互联网上到处都是关于“鸭子类型”的文章,您可以找到任何数量的细节。 但当我们尝试撰写此类主题时,我们面临的挑战是以尽可能简单的方式深入了解这个概念。
正如我的一位老师曾经说过“如果你的学生无法理解一个概念,试着猜测问题出在他的学习还是你的教学”

评论很好,Anurag Gupta。 在像 Python 这样的语言中,我们离执行“底层”代码的机器相当远,而且在任何情况下,类型化更多的是关于我们对结果的解释,而不是机器本身。

回复  作者:Anurag Gupta (未验证)

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 许可。
© . All rights reserved.