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 在 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 (未验证)

© . All rights reserved.