【问题标题】:Why doesn't Python spot errors before execution?为什么 Python 在执行前没有发现错误?
【发布时间】:2016-10-11 21:13:41
【问题描述】:

假设我在 Python 中有以下代码:

a = "WelcomeToTheMachine"
if a == "DarkSideOfTheMoon":
    awersdfvsdvdcvd
print "done!"

为什么没有这个错误?它甚至是如何编译的?在 Java 或 C# 中,这会在编译过程中被发现。

【问题讨论】:

  • 这里是a!="DarkSideOfTheMoon",所以 if 条件的主体永远不会被执行。
  • 因为在执行之前不会出错。
  • 你偶然发现的是语法错误和运行时错误之间的区别......
  • 我建议使用像 PyCharm 这样好的 IDE 来帮助您发现此类错误。

标签: python


【解决方案1】:

Python 不是一种编译语言,这就是您的代码不会引发编译错误的原因。

Python 是一种字节码解释语言。从技术上讲,源代码被“编译”为字节码,但是如果使用 PyPy 或 Pyston,则字节码会被及时(JIT)编译,否则它会被逐行解释。

工作流程如下:

您的 Python 代码 -> 编译器 -> .pyc 文件 -> 解释器 -> 你的输出

使用标准的python运行时这一切意味着什么?基本上所有繁重的工作都发生在运行时,这与 C 或 C++ 不同,在 C 或 C++ 中,整个源代码在编译时被分析并转换为二进制文件。

【讨论】:

  • 有点编译,实际的语法错误实际上是在编译过程中发现的。只是在更多静态语言中,编译阶段的许多事情都发生在 Python 的运行时。
  • 另外,Python 的标准实现不做 JIT。 PyPy 执行 JIT,但大多数人没有使用 PyPy。
  • “逐行解释”部分是某人的想象。当 cpython VM 运行字节码时,“行”只是为诊断目的而保留的行号。就虚拟机而言,没有线路。并且代码不会“技术上”被编译。它在很大程度上被编译成 VM 指令流,就像 C# 或 Java 编译器一样!甚至还有一个优化器,并且正在进行使 VM 更快的工作。没有翻译。
【解决方案2】:

在“编译”期间,python 几乎只检查您的语法。由于awersdfvsdvdcvd 一个有效的标识符,因此在该行实际执行之前不会引发错误。仅仅因为您使用未定义的名称并不意味着它不能在其他地方定义......例如:

globals()['awersdfvsdvdcvd'] = 1

文件的前面部分足以抑制NameError,如果执行名称拼写错误的行会出现这种情况。

好的,那么 python 不能只查找全局语句吗?答案仍然是“否”——从模块“foo”,我可以以类似的方式添加到模块“bar”的全局变量中。而且 python 在实际运行之前无法知道要导入或将要导入哪些模块(我也可以在运行时动态导入模块)。


请注意,我提到的 Python 作为一门语言不能向您发出警告的大多数原因都涉及到人们在做疯狂的事情。有许多工具警告你这些事情(假设你不会做那样愚蠢的事情)。我最喜欢的是pylint,但几乎任何 python linter 都应该能够警告你未定义的变量。如果您将 linter 连接到您的编辑器,大多数情况下您可以在实际运行代码之前发现这些错误。

【讨论】:

    【解决方案3】:

    因为 Python 是一种解释型语言。这意味着如果 Python 的解释器没有到达该行,它不会产生任何错误。

    【讨论】:

    • Python 被编译成字节码,所以它实际上是事先遍历了整个源代码。 Python被编译,字节码通常被解释。
    【解决方案4】:

    没有什么可发现的:就 Python 语言而言,这不是一个“错误”。你有一个完全有效的 Python 程序。 Python 是一种动态语言,您使用的标识符会在运行时解析。

    用 C#、Java 或 C++ 编写的等效程序将是无效的,因此编译会失败,因为在所有这些语言中,都需要使用未定义的标识符来为用户生成诊断(即编译时错误)。在 Python 中,根本不知道该标识符在编译时是否已知。所以代码是有效的。可以这样想:在 Python 中,拥有一个建筑工地的地址(一个名字)并不要求建筑已经开始。重要的是,当您使用地址(名称)时,就好像那里有建筑物一样,最好有建筑物,否则会引发异常:)

    在 Python 中,会发生以下情况:

    a = "WelcomeToTheMachine" 查找属性a 的封闭上下文(这里:模块上下文),并将属性'a' 设置为存储在常量池中的给定字符串对象。它还会缓存属性引用,以便后续访问a 会更快。

    if a == "DarkSideOfTheMoon": 在缓存中找到a,并对对象a 执行二进制比较运算符。这最终以builtins.str.__eq__ 结束。该运算符的返回值用于控制程序流程。

    awersdfvsdvdcvd 是一个表达式,其值是查找名称 'awersdfvsdvdcvd' 的结果。这个表达式被评估。在您的情况下,在封闭的上下文中找不到该名称,并且查找引发了 NameError 异常。

    此异常传播到匹配的异常处理程序。由于处理程序位于当前模块中的所有嵌套代码块之外,print 函数永远不会有机会被调用。 Python 的内置异常处理程序向用户发出错误信号。解释器(用词不当!)实例无事可做。由于 Python 进程在解释器实例完成后不会尝试做任何其他事情,因此它会终止。

    绝对没有说程序导致运行时错误。例如,awersdfvsdvdcvd 可以在模块执行之前设置在封闭范围内,这样就不会引发运行时错误。 Python 允许对模块的生命周期进行精细控制,并且您的代码可以在模块编译后但在执行之前注入 awersdfvsdvdcvd 的值。只需几行相当简单的代码即可。

    事实上,这是 Python 程序中使用的众多动态编程技术之一。它们的明智使用使 C++ 在几十年或永远不会原生获得的功能成为可能,而这在 C# 和 Java 中都非常麻烦。当然,Python 有性能成本——没有什么是免费的。

    如果您希望在编译时突出显示此类问题,您可以轻松地将一些工具集成到 IDE 中来发现此问题。例如。 PyCharm 有一个内置的静态检查器,这个错误会像预期的那样用红色波浪线突出显示。

    【讨论】:

      猜你喜欢
      • 2020-04-07
      • 1970-01-01
      • 2012-08-04
      • 1970-01-01
      • 1970-01-01
      • 2023-02-08
      • 1970-01-01
      • 2015-10-06
      • 1970-01-01
      相关资源
      最近更新 更多