31

Python存在静态分析工具,但编译时检查往往与Python 所拥护的运行时绑定理念截然相反。可以使用静态分析工具包装标准 Python 解释器来强制执行一些“使用严格”类的约束,但我们没有看到这种东西被广泛采用。

Python 是否有某些东西使“使用严格”行为变得不必要或特别不受欢迎?

或者,尽管 Perl 被广泛采用,但“使用严格”行为是否不必要?

注意:“必要”是指“实际上必要”,而不是绝对必要。显然你可以不使用“use strict”来编写 Perl,但是(据我所见)大多数 Perl 程序员确实使用它。

注意:Python 解释器包装器不需要类似“use strict”的约束——您可以使用类似于“use strict”的伪编译指示,普通解释器会忽略它。我不是在谈论添加语言级别的功能。


更新:根据评论解释 Perl 中“使用严格”的作用。(官方文档的链接在第一段。)

“use strict”指令具有三个不同的组件,其中只有两个非常有趣:

  • use strict vars:静态检查程序中词法范围的变量使用情况。(请记住,在 Python 中,基本上只有global作用域和local作用域)。许多 Python linter 会检查这类事情。因为这是他们唯一可以做的静态分析,所以 linter 假设你使用简单的词法作用域,并警告你在这种意义上看起来错误的事情,直到你告诉他们闭嘴。IE

    FOO = 12
    foo += 3
    

    如果您没有对命名空间做任何花哨的事情,这对于检查拼写错误很有用。

  • use strict refs:防止符号命名空间取消引用。Python 最接近的类比是使用locals()globals()进行符号绑定和标识符查找。

  • 使用严格的潜艇:Python 中没有真正的模拟。

4

10 回答 10

37

好吧,我不是一个 Python 程序员,但我会说答案是“是”。

任何允许您随时创建具有任何名称的变量的动态语言都可以使用“严格”编译指示。

Perl 中的严格变量(Perl 中的严格选项之一,“使用严格”将它们全部打开)要求在使用之前声明所有变量。这意味着这段代码:

my $strict_is_good = 'foo';
$strict_iS_good .= 'COMPILE TIME FATAL ERROR';

在编译时生成致命错误。

我不知道如何让 Python 在编译时拒绝此代码:

strict_is_good = 'foo';
strict_iS_good += 'RUN TIME FATAL ERROR';

您将得到一个strict_iS_good未定义的运行时异常。但仅在代码执行时。如果您的测试套件没有 100% 的覆盖率,您可以轻松发布此错误。

每当我使用一种没有这种行为的语言(例如 PHP)工作时,我都会感到紧张。我不是一个完美的打字员。一个简单但难以发现的错字可能会导致您的代码以难以追踪的方式失败。

因此,重申一下,YES Python 可以使用“严格”编译指示来打开编译时检查,以检查可以在编译时检查的内容。我想不出要添加的任何其他检查,但更好的 Python 程序员可能会想到一些。

请注意,我专注于 Perl 中 stict vars 的实用效果,并忽略了一些细节。如果您真的想了解所有详细信息,请参阅perldoc for strict

更新:对一些评论的回应

Jason Baker:像 pylint 这样的静态检查器很有用。但它们代表了一个可以并且经常被跳过的额外步骤。在编译器中构建一些基本检查可确保始终如一地执行这些检查。如果这些检查可以通过编译指示来控制,那么即使是与检查成本相关的反对意见也变得毫无意义。

popcnt:我知道 python 会生成运行时异常。我说了这么多。我提倡尽可能进行编译时检查。请重读帖子。

mpeters:没有任何计算机分析代码可以发现所有错误——这相当于解决了停机问题。更糟糕的是,要找到分配中的拼写错误,您的编译器需要知道您的意图并找到您的意图与代码不同的地方。这显然是不可能的。

然而,这并不意味着不应该进行检查。如果存在易于检测的问题类别,那么捕获它们是有意义的。

我对 pylint 和 pychecker 不够熟悉,无法说出它们会捕获哪些类型的错误。正如我所说,我对 python 非常缺乏经验。

这些静态分析程序很有用。但是,我相信除非它们复制编译器的功能,否则编译器将始终比任何静态检查器都能够“了解”更多关于程序的信息。尽可能不利用这一点来减少错误似乎很浪费。

更新 2:

cdleary - 理论上,我同意你的看法,静态分析器可以做任何编译器可以做的验证。在 Python 的情况下,它应该足够了。

但是,如果您的编译器足够复杂(尤其是如果您有很多编译指示会改变编译的发生方式,或者如果像 Perl 一样,您可以在编译时运行代码),那么静态分析器必须接近编译器/解释器的复杂性以做分析。

嘿,所有这些关于复杂编译器和编译时运行代码的讨论都显示了我的 Perl 背景。

我的理解是Python没有编译指示,不能在编译时运行任意代码。因此,除非我错了或添加了这些功能,否则静态分析器中相对简单的解析器就足够了。在每次执行时强制进行这些检查肯定会有所帮助。当然,我这样做的方式是使用编译指示。

一旦您将 pragma 添加到组合中,您就开始走下一个滑坡,并且分析器的复杂性必须与您在 pragma 中提供的功能和灵活性成正比。如果你不小心,你可能会像 Perl 一样结束,然后“只有 python 可以解析 Python”,这是我不想看到的未来。

也许命令行开关是添加强制静态分析的更好方法;)

(当我说 Python 不能像 Perl 那样适应编译时行为时,绝不是要批评 Python 的功能。我有一种预感,这是一个经过仔细考虑的设计决策,我可以从中看到智慧。Perl 的恕我直言,编译时的极大灵活性是该语言的一大优势和一个可怕的弱点;我也看到了这种方法的智慧。)

于 2009-03-05T15:37:14.737 回答
11

Python 确实有一些可以改变脚本语法的东西:

from __future__ import print_function

以及具有语法含义的各种其他未来功能。只是 Python 的语法比历史上的 Perl 更严格、更稳定、定义更明确;'strict refs' 和 'strict subs' 禁止的东西在 Python 中从未存在过。

'strict vars' 主要是为了阻止输入错误的引用和遗漏的 'my's 创建意外的全局变量(好吧,Perl 术语中的包变量)。这在 Python 中不会发生,因为裸分配默认为本地声明,裸未分配符号会导致异常。

(仍然存在这样的情况,即用户不小心尝试直写到全局而不使用“全局”语句声明它,从而导致意外的本地错误,或者更常见的是 UnboundLocalError。这往往很快就能学会,但它这是一个有争议的案例,必须声明你的本地人可能会有所帮助。尽管很少有经验丰富的 Python 程序员会接受可读性的负担。)

其他不涉及语法的语言和库更改通过警告系统处理。

于 2009-03-05T02:54:53.507 回答
9

“Python 所拥护的运行时绑定哲学......使得“使用严格”的行为变得不必要 [并且] 特别不受欢迎”

很不错的总结。谢谢。

基本上就是这样。静态分析工具对 Python 的帮助不足以值得。


编辑

“我要求我们反思为什么我们不需要它,以及为什么 Perl 程序员认为他们确实需要它。”

原因正是你已经给出的原因。我们不需要它,因为它没有帮助。显然,你不喜欢这个答案,但没有什么可说的了。编译时或预编译时检查根本没有帮助。

但是,由于您花时间再次提出问题,我将为您已经给出的答案提供更多证据。

我写 Java 的次数几乎和我写 Python 的次数一样多。Java 的静态类型检查并不能防止任何逻辑问题;它不利于满足性能要求;它无助于满足用例。它甚至不会减少单元测试的数量。

虽然静态类型检查确实会发现偶尔滥用方法的情况,但您在 Python 中也能很快发现这一点。在 Python 中,您会在单元测试时找到它,因为它不会运行。注意:我并不是说通过大量聪明的单元测试发现错误类型,我是说大多数错误类型问题是通过未处理的异常发现的,在这些异常中,事情根本无法运行到足以测试断言。

Pythonistas 不在静态检查上浪费时间的原因很简单。我们不需要它。它没有提供任何价值。这是一个没有经济利益的分析水平。它不再使我能够解决真实的人在使用真实数据时遇到的实际问题。

查看与语言(不是问题域或库)相关的最流行的 SO Python 问题。

“foo is None”和“foo == None”之间有什么区别吗?——==is。没有静态检查可以帮助解决这个问题。另外,请参阅Python 中的 `==` 和 `is` 之间有区别吗?

**(双星)和*(星)对参数有什么作用?--*x给出一个列表,**x给出一个字典。如果您不知道这一点,那么当您尝试执行不适合这些类型的操作时,您的程序会立即终止。“如果你的程序从不做任何‘不恰当’的事情怎么办?”。然后你的程序就可以工作了。'纳夫说。

如何在 Python 中表示“枚举”?——这是对某种有限域类型的请求。具有类级别值的类几乎可以完成这项工作。“如果有人更改作业怎么办”。易于构建。重写__set__以引发异常。是的,静态检查可能会发现这一点。不,实际上不会有人对枚举常量和变量感到困惑。当他们这样做时,很容易在运行时发现。“如果逻辑永远不会被执行怎么办”。嗯,这是糟糕的设计和糟糕的单元测试。抛出编译器错误并放入从未测试过的错误逻辑并不比从未测试过的动态语言中发生的事情更好。

生成器表达式与列表理解——静态检查无助于解决这个问题。

为什么 1+++2 = 3?-- 静态检查不会发现这一点。尽管进行了所有编译器检查,C 中的 1+++2 是完全合法的。它在 Python 中与在 C 中不同,但同样合法。同样令人困惑。

列表列表的变化意外地反映在子列表中——这完全是概念性的。静态检查也无助于解决这个问题。Java 等价物也会编译并表现不佳。

于 2009-03-05T03:36:24.453 回答
7

从我看到的评论来看,我认为“使用严格”的做法有些混乱。它不会打开编译时类型检查(就像 Java 一样)。从这个意义上说,Perl 程序员与 python 程序员是一致的。正如 S.Lott 上面所说,这些类型的检查不能防止逻辑错误,不要减少您需要编写的单元测试的数量,而且我们也不是束缚编程的忠实拥护者。

以下是“use strict”的作用列表:

  1. 使用符号引用是一个运行时错误。这可以防止你做疯狂(但有时有用的事情,比如)

    $var = 'foo';

    $foo = 'bar';

    print $$var; # this would contain the contents of $foo unless run under strict

  2. 使用未声明的变量是一个运行时错误(这意味着您需要在使用之前使用“my”、“our”或“local”来声明变量的范围。

  3. 所有的barewords都被认为是编译时语法错误。裸词是没有被声明为符号或子程序的词。这主要是为了取缔历史上做过但被认为是错误的事情。

于 2009-03-05T12:27:03.430 回答
5

这个原始答案是正确的,但也许不能从实际意义上解释这种情况。

Python 存在静态分析工具,但编译时检查往往与 Python 所拥护的运行时绑定理念截然相反。

Perl 中的 'use strict' 提供了确保拼写错误或变量名(通常)在编译时被捕获的能力。这确实提高了代码的可靠性,并加快了开发速度。但是为了让这样的事情有价值,你需要声明变量。而 Python 风格似乎不鼓励这种做法。

因此,在 Python 中,您永远不会发现变量拼写错误,除非您在运行时注意到您认为自己所做的赋值没有被执行,或者表达式似乎解析为意外的值。捕捉此类错误可能非常耗时,尤其是当程序变大时,以及人们被迫维护他人开发的代码时。

Java 和 C/C++ 更进一步,包括类型检查。动机是实际的,而不是哲学的。如何尽快捕获尽可能多的错误,并确保在将代码发布到生产环境之前消除所有错误?每种语言似乎都采取了特定的策略并根据他们认为重要的内容来运行它。在像 Perl 这样不支持运行时绑定的语言中,利用“use strict”来简化开发是有意义的。

于 2010-11-10T00:28:43.933 回答
4

Python 没有真正的词法作用域,所以严格的变量不是很明智。它没有符号引用 AFAIK,因此不需要严格的引用。它没有裸词,因此不需要严格的变量。

老实说,我想念的只是词汇范围。另外两个我认为是 Perl 中的疣。

于 2009-03-05T12:25:19.517 回答
3

正如您所暗示的,我认为'use strict'in Perl 更像是一个编译指示:它改变了编译器的行为。

Perl 语言哲学不同于 python 哲学。就像在 Perl 中,你得到了足够多的绳子来反复吊死自己。

Larry Wall 对语言学很感兴趣,所以我们从 Perl 中得到了所谓的 TIMTOWDI(比如tim-toe-dee)原理与 python 的 Zen:

应该有一种——最好只有一种——明显的方法来做到这一点。

你可以很容易地使用 pylint 和 PyChecker 来提出你自己的use strictfor python 风格(或类似的东西perl -cw *scriptname*),但由于语言设计中的不同理念,你不会在实践中广泛遇到这种情况。

根据您对第一张海报的评论,您熟悉 python 的import this. 里面有很多东西可以说明为什么你use strict在 Python 中看不到类似的东西。如果你冥想《Python 之禅》中的公案,你可能会为自己找到启迪。:)

于 2009-03-05T02:57:31.660 回答
1

我发现我只关心检测对未声明变量的引用。Eclipse 通过 PyDev 集成了 pylint,虽然 pylint 远非完美,但它在这方面做得很合理。

它确实有点违背 Python 的动态特性,当我的代码变得聪明时,我确实不得不偶尔添加#IGNOREs。但我发现这种情况很少发生,我对此很满意。

但是我可以看到一些类似 pylint 的功能以命令行标志的形式变得可用。有点像 Python 2.6 的 -3 开关,用于识别 Python 2.x 和 3.x 代码之间的不兼容点。

于 2009-03-05T19:25:17.403 回答
0

在 Perl 中不使用“use strict”来编写大型程序是非常困难的。如果没有'use strict',如果你再次使用一个变量,并且因为遗漏了一个字母而拼错了它,程序仍然可以运行。如果没有测试用例来检查你的结果,你永远找不到这样的错误。由于这个原因,找出您得到错误结果的原因可能非常耗时。

我的一些 Perl 程序由 5,000 到 10,000 行代码组成,分成几十个模块。如果没有“使用严格”,就无法真正进行生产编程。我绝不允许使用不强制“变量声明”的语言在工厂中安装生产代码。

这就是为什么 Perl 5.12.x 现在将“use strict”作为默认行为。您可以关闭它们。

由于没有强制执行变量声明,PHP 给了我很多问题。所以你需要限制自己使用这种语言的小程序。

只是一个意见...

abc解析

于 2011-07-18T23:44:44.347 回答
-1

正如他们所说,Perl 是一种无拘无束的语言 :) 。所以你可以在宣布之前使用变量;例如:如果你使用 var name "is_array" 但输入 "is_arrby" ,编译器不会在没有 "use strict" 的情况下报告错误。因此,在 perl 中编写长程序时,最好使用“use strict”语句。当然,运行一次脚本少于 50 行,没有必要:)

于 2014-08-06T03:10:24.523 回答