Press "Enter" to skip to content

不要忘记Python是动态的!

PYTHON编程

越来越多的静态和动态检查…… Python是否朝着我们想要的方向前进?

Python将前往何处?由Jamie Templeton在Unsplash上拍摄的照片

Python是一种动态语言。然而,近年来,越来越多的关注点放在了静态类型检查上。这反过来又导致了对运行时类型检查的增加兴趣。我们将走多远呢?在本文中,我们将回顾为什么Python曾经被认为是一种强大的动态类型编程语言。

现在还是吗?

Python的优势一直在于其简单性,这至少部分是由于动态类型而导致的,不仅因为我们可以在REPL中编写Python代码,还因为以下原因:

  • 您可以轻松更改程序中变量的类型。
  • 您不必定义变量的类型。
  • 代码易于阅读和理解。
  • 有时,您可以使用几行代码来实现相当复杂的算法。静态类型语言通常需要更多的代码行数。

当然,动态类型不是没有代价的。第一个是性能降低,这是我们都知道的。这种降低来自于事实:在运行时(由Python完成),必须检查未声明的类型。另一个成本是运行时错误的风险增加:由于类型不是在编译时而是在运行时检查的,相关错误在程序执行期间而不是在编译期间抛出。

我们需要记住,Python提供了一组工具来处理其性能降低问题:

Python的速度:不那么糟糕!

我经常听说Python太慢了。是吗?

小猪AI.com

多年来,Pythonistas为Python是一种动态类型语言而感到自豪和高兴。当然,那些不喜欢Python的人认为它是一种糟糕的语言……我能说什么?每个人都可以自由地思考他们想要的;编程是一个自由的世界。

编程是一个自由的世界。

然而,近年来,Python一直在向编程的静态方向发展。这一运动最重要的方面是类型提示。当然,它们是可选的,但现在编写的大型项目都实现了类型提示。你更经常会听到在严肃的项目中你必须提示类型,而不是你不必提示类型——更不用说你不应该提示类型。准备好听到这样的话:“当然,类型提示是可选的,所以对于原型和短脚本,您不需要使用它们,但对于大型项目来说,除了提示类型之外,没有其他选择。”我听到过不止一次。

整个情况引出了以下问题:我们确实需要所有这些类型提示、静态类型检查器和运行时类型检查器吗?

我不打算回答这个问题。这主要是因为我远远不是那些认为自己对……呃,对一切或几乎一切都了解的人之一。但是我希望邀请您自己思考这个问题。

然而,我会提醒您——也提醒自己——Python的动态类型,也称为鸭子类型,是这种语言成功的原因。以下是鸭子类型如何工作的流行解释:

如果它走路像鸭子,嘎嘎叫像鸭子,那么它一定是鸭子。

在没有类型提示和运行时类型检查的情况下,鸭子类型可以非常强大。我将在非常简单的示例中向您展示这一点,而无需再多说,让我们跳入这个简单的示例中:

作者发布的运行时类型检查错误截图。图片由作者提供

在这里,我们正在检查 xy 的类型,两者都应该是字符串( str )。请注意,这种方式,我们有点在检查我们提供给 str.join() 方法的是否是 tuple[str, str] 。当然,我们不必检查这个方法是否得到了一个元组,因为我们自己正在创建它;只需要检查 xy 的类型即可。当它们中的任何一个不是字符串时,函数将引发 TypeError 并显示一个简单的消息:"提供一个字符串!"

很好,是吗?我们可以确保该函数仅在正确类型的值上运行。如果不是,我们将看到一条定制消息。我们也可以使用自定义错误:

我们应该在 Python 中使用自定义异常吗?

Python 有很多内置异常,我们很少需要创建和使用自定义异常。或者我们需要吗?

towardsdatascience.com

现在,让我们删除类型检查,看看这个函数如何工作:

Catching errors without runtime type checking; the message is not custom by built-in. Image by author

嗨。它似乎以相似的方式工作…我的意思是,异常基本上是在同一个地方引发的,所以我们并没有冒险。所以…

确实,在这里,函数 foo_no_check() 使用了鸭子类型,它使用了隐式类型的概念。在这个例子中,str.join() 方法假定它接受一个字符串的元组,因此 xy 都必须是字符串,如果它们不是,那么 tuple[str, str] 的隐式类型就没有被实现。因此出现了错误。

你可能会说:“但是嘿!看看消息!之前,我们可以使用自定义消息,现在我们不能!”

我们确实不能吗?看看:

Catching errors without runtime type checking; the error message is customized. Image by author

我们现在可以看到两个消息:内置的( sequence item 1: expected str instance, in found )和自定义的( 提供字符串! )。

性能

你可能会问:有什么区别?所以,我检查类型。问题在哪里?

好吧,有相当大的区别:性能。让我们使用 perftester 包对函数的三个版本进行基准测试:

用 perftester 进行 Python 函数基准测试的简便方法

你可以使用 perftester 简便地对 Python 函数进行基准测试

towardsdatascience.com

这里是基准测试结果:

Benchmarks performed using the perftester package. Image by author

在本文的所有基准测试中,我在 Windows 10 机器上使用了 WSL 1,32GB 的 RAM 和四个物理(八个逻辑)核心的 Python 3.11。

在第二行中,我将默认实验次数设置为10,在每次运行中,每个函数要运行一亿次。我们从十个运行中选出最好的,并报告平均时间(以秒为单位)。

foo()函数,即具有运行时类型检查的函数,比其他两个函数慢得多。 foo_no_check()函数是最快的,虽然foo_no_check_tryexcept()稍微慢一点。

结论?运行时类型检查是昂贵的。

你可能会说:“什么?你在开玩笑吧?昂贵?只是一小部分秒数!甚至不到一微秒!”

确实。这不算太多。但是这是一个非常简单的函数,只有两个检查。现在想象一下一个大型代码库,有许多类,方法和函数 – 还有很多运行时类型检查。有时,这可能意味着性能显着降低。

运行时类型检查是昂贵的。

结论

当阅读关于鸭子类型的文章时,你通常会看到猫喵喵叫的例子,狗不会叫,奶牛会哞哞叫。当你听到一只动物喵喵叫时,它既不是狗也不是奶牛,它是一只猫。但不是老虎。我决定使用一个非典型的例子,希望你能清楚地看到鸭子类型的优点。

正如你所看到的,Python的异常处理在运行时类型检查方面做得很好。当需要时,您可以通过添加其他类型检查来帮助它,但始终记住它们会添加一些开销时间。

结论?Python具有出色的异常处理工具,可以很好地工作。通常情况下,我们根本不需要使用运行时类型检查。然而,有时我们可能需要。当两种类型具有相似的接口时,鸭子类型可能会失败。

例如,想象一下您想要添加两个数字(x + y),但用户提供了两个字符串。这不会导致错误,因为您可以添加两个字符串。在这种情况下,如果您不想程序继续使用这些不正确的值,则可能需要添加运行时类型检查。迟早它也可能会出错,所以问题是是否希望程序一直继续到那时。如果是,您可能会冒险在程序流中稍后引发异常,因此添加类型检查实际上可以帮助节省时间。此外,在程序流后面引发异常可能意味着找到错误的真正原因会带来困难。

总之,我不想告诉您永远不应该使用运行时类型检查。但通常情况下,当不需要时会添加此类检查。

我希望我引起了你的兴趣并启发了你。今天,这就是我想要实现的全部内容。请在评论中分享您的想法。告诉我们在这种简单情况下是否会使用运行时类型检查。

我还没有讨论过静态检查和鹅类型。我已经写了几篇关于静态检查和类型提示的文章:

Python的类型提示:朋友,敌人还是头疼?

类型提示在Python社区中的受欢迎程度正在增加。这将引导我们到哪里?我们可以做些什么来使用它……

betterprogramming.pub

Python类型提示:鸭子类型兼容性和一致性

当你提示浮点数时,你不必提示int,当你提示元组时,你不必提示命名元组。为什么?

towardsdatascience.com

Python类型提示:从类型别名到类型变量和新类型

看到类型别名,类型变量和新类型的实际应用

towardsdatascience.com

我还没有写关于鹅类型的文章,但迟早会到来的。

Python 仍然被认为是一种强大的动态类型语言吗?老实说,我不知道。Python 的很多关注点都在静态和运行时检查上,以至于我担心很多人已经忘记了 Python 的真正优势在于完全不同的事情上:它的简单性、可读性,以及是的,鸭子类型。

然而,我听说过一些有经验的 Python 程序员表达了他们对 Python 不再是以前那样的感到难过。他们中的一些人决定从 Python 转向其他编程语言,声称:“如果我想用一种需要为变量定义类型的语言,Python 将是我的最后选择!” 这是很有道理的。静态类型语言可以比 Python 快得多——而且它们仍然可以像 Go 一样简单易读。但是 Python… Python 提供了简单而强大的鸭子类型… 很多人似乎忘记了鸭子类型。

我自己喜欢 Python 和 Go。Go 是静态类型的,这也是它比 Python 更快的原因之一。但是,你知道吗?对我来说,Go 的静态类型代码通常比带有类型提示的 Python 代码更容易阅读和理解!

当我看到代码中到处都是运行时检查时,我感到疲倦和沮丧。这不是 Python 的创造目的。是的,类型提示可能很有帮助——但只有在正确使用和不过度使用的情况下才是如此。如果过度使用,它们可能会成为很大的负担。

当我看到代码中到处都是运行时检查时,我感到疲倦和沮丧。这不是 Python 的创造目的。

感谢您的阅读。如果您喜欢这篇文章,您可能也会喜欢我写的其他文章;您可以在这里看到它们。如果您想加入小猪AI,请使用我的推荐链接:

在小猪AI上阅读 Marcin Kozak 的每个故事(以及成千上万其他作家的故事)。您的会员费直接支持……

小猪AI.com

Leave a Reply

Your email address will not be published. Required fields are marked *