PYTHON编程
越来越多的静态和动态检查…… Python是否朝着我们想要的方向前进?
Python是一种动态语言。然而,近年来,越来越多的关注点放在了静态类型检查上。这反过来又导致了对运行时类型检查的增加兴趣。我们将走多远呢?在本文中,我们将回顾为什么Python曾经被认为是一种强大的动态类型编程语言。
现在还是吗?
Python的优势一直在于其简单性,这至少部分是由于动态类型而导致的,不仅因为我们可以在REPL中编写Python代码,还因为以下原因:
- 您可以轻松更改程序中变量的类型。
- 您不必定义变量的类型。
- 代码易于阅读和理解。
- 有时,您可以使用几行代码来实现相当复杂的算法。静态类型语言通常需要更多的代码行数。
当然,动态类型不是没有代价的。第一个是性能降低,这是我们都知道的。这种降低来自于事实:在运行时(由Python完成),必须检查未声明的类型。另一个成本是运行时错误的风险增加:由于类型不是在编译时而是在运行时检查的,相关错误在程序执行期间而不是在编译期间抛出。
我们需要记住,Python提供了一组工具来处理其性能降低问题:
Python的速度:不那么糟糕!
我经常听说Python太慢了。是吗?
小猪AI.com
多年来,Pythonistas为Python是一种动态类型语言而感到自豪和高兴。当然,那些不喜欢Python的人认为它是一种糟糕的语言……我能说什么?每个人都可以自由地思考他们想要的;编程是一个自由的世界。
编程是一个自由的世界。
然而,近年来,Python一直在向编程的静态方向发展。这一运动最重要的方面是类型提示。当然,它们是可选的,但现在编写的大型项目都实现了类型提示。你更经常会听到在严肃的项目中你必须提示类型,而不是你不必提示类型——更不用说你不应该提示类型。准备好听到这样的话:“当然,类型提示是可选的,所以对于原型和短脚本,您不需要使用它们,但对于大型项目来说,除了提示类型之外,没有其他选择。”我听到过不止一次。
整个情况引出了以下问题:我们确实需要所有这些类型提示、静态类型检查器和运行时类型检查器吗?
我不打算回答这个问题。这主要是因为我远远不是那些认为自己对……呃,对一切或几乎一切都了解的人之一。但是我希望邀请您自己思考这个问题。
然而,我会提醒您——也提醒自己——Python的动态类型,也称为鸭子类型,是这种语言成功的原因。以下是鸭子类型如何工作的流行解释:
如果它走路像鸭子,嘎嘎叫像鸭子,那么它一定是鸭子。
在没有类型提示和运行时类型检查的情况下,鸭子类型可以非常强大。我将在非常简单的示例中向您展示这一点,而无需再多说,让我们跳入这个简单的示例中:
在这里,我们正在检查 x
和 y
的类型,两者都应该是字符串( str
)。请注意,这种方式,我们有点在检查我们提供给 str.join()
方法的是否是 tuple[str, str]
。当然,我们不必检查这个方法是否得到了一个元组,因为我们自己正在创建它;只需要检查 x
和 y
的类型即可。当它们中的任何一个不是字符串时,函数将引发 TypeError
并显示一个简单的消息:"提供一个字符串!"
。
很好,是吗?我们可以确保该函数仅在正确类型的值上运行。如果不是,我们将看到一条定制消息。我们也可以使用自定义错误:
我们应该在 Python 中使用自定义异常吗?
Python 有很多内置异常,我们很少需要创建和使用自定义异常。或者我们需要吗?
towardsdatascience.com
现在,让我们删除类型检查,看看这个函数如何工作:
嗨。它似乎以相似的方式工作…我的意思是,异常基本上是在同一个地方引发的,所以我们并没有冒险。所以…
确实,在这里,函数 foo_no_check()
使用了鸭子类型,它使用了隐式类型的概念。在这个例子中,str.join()
方法假定它接受一个字符串的元组,因此 x
和 y
都必须是字符串,如果它们不是,那么 tuple[str, str]
的隐式类型就没有被实现。因此出现了错误。
你可能会说:“但是嘿!看看消息!之前,我们可以使用自定义消息,现在我们不能!”
我们确实不能吗?看看:
我们现在可以看到两个消息:内置的( sequence item 1: expected str instance, in found
)和自定义的( 提供字符串!
)。
性能
你可能会问:有什么区别?所以,我检查类型。问题在哪里?
好吧,有相当大的区别:性能。让我们使用 perftester
包对函数的三个版本进行基准测试:
用 perftester 进行 Python 函数基准测试的简便方法
你可以使用 perftester 简便地对 Python 函数进行基准测试
towardsdatascience.com
这里是基准测试结果:
在本文的所有基准测试中,我在 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上阅读 Marcin Kozak 的每个故事(以及成千上万其他作家的故事)。您的会员费直接支持……
小猪AI.com