Python 中的鸭子类型是什么?

Python 中数据类型有不同的哲学。
113 位读者喜欢这个。

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 Programming: Problem Solving, Packages and Libraries》,由 McGraw Hill 出版。您可以通过 999.anuraggupta at Gmail 联系到他,他很乐意听到读者关于 Python 相关主题的来信。

9 条评论

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

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

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

回复 作者:Alessandro (未验证)

一篇写得很好的文章!谢谢你 Anurag。继 Peter Cheer 上面的评论之后,我认为如果你再写一篇关于“鸭子类型导致的常见错误以及如何避免这些错误”的文章,那就太棒了。

我使用 Python 编程已经快 20 年了,我注意到的主要一点是,如果不小心,EAFP 很容易导致非常隐晦的错误消息。

urwid(The Python TUI 框架)就深受其害。

(如果您在构建小部件树时没有按照它的期望输入,您将在 urwid 深处得到一个关于缺少属性/方法的非常隐晦的错误。)

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

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

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

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

知识共享许可协议本作品根据知识共享署名-相同方式共享 4.0 国际许可协议获得许可。
© . All rights reserved.