使用 Python 打印不带括号的不同错误消息 3

Printing without parentheses varying error message using Python 3

当我尝试在 Python 3.4 的简单名称上使用不带括号的 print 时,我得到:

>>> print max
Traceback (most recent call last):
  ...
  File "<interactive input>", line 1
    print max
            ^
SyntaxError: Missing parentheses in call to 'print'

好的,现在我明白了,我只是忘了移植我的 Python 2 代码。

但是现在当我尝试打印函数的结果时:

>>> print max([1,2])
Traceback (most recent call last):
    ...
    print max([1,2])
            ^
SyntaxError: invalid syntax

或者:

print max.__call__(23)
        ^
SyntaxError: invalid syntax

(请注意,在这种情况下,光标指向第一个点之前的字符。)

消息不同(并且有点误导,因为标记位于 max 函数下方)。

为什么 Python 不能及早发现问题?

注意:这个问题的灵感来自围绕这个问题的困惑:Pandas read.csv syntax error,一些 Python 专家错过了真正的问题,因为误导性错误消息。

用作语句而不是函数的 print 的特殊异常消息实际上是作为 特例 .

实现的

粗略地说,当创建 SyntaxError 时,它会调用一个特殊函数来检查 print 语句 基于 异常是指。

但是,function (the one responsible for the "Missing parenthesis" error message) 中的第一个测试是行中是否有左括号。我复制了该函数的源代码 (CPython 3.6.4),并用 "arrows":

标记了相关行
static int
_report_missing_parentheses(PySyntaxErrorObject *self)
{
    Py_UCS4 left_paren = 40;
    Py_ssize_t left_paren_index;
    Py_ssize_t text_len = PyUnicode_GET_LENGTH(self->text);
    int legacy_check_result = 0;

    /* Skip entirely if there is an opening parenthesis <---------------------------- */
    left_paren_index = PyUnicode_FindChar(self->text, left_paren,
                                          0, text_len, 1);
    if (left_paren_index < -1) {
        return -1;
    }
    if (left_paren_index != -1) {
        /* Use default error message for any line with an opening parenthesis <------------ */
        return 0;
    }
    /* Handle the simple statement case */
    legacy_check_result = _check_for_legacy_statements(self, 0);
    if (legacy_check_result < 0) {
        return -1;

    }
    if (legacy_check_result == 0) {
        /* Handle the one-line complex statement case */
        Py_UCS4 colon = 58;
        Py_ssize_t colon_index;
        colon_index = PyUnicode_FindChar(self->text, colon,
                                         0, text_len, 1);
        if (colon_index < -1) {
            return -1;
        }
        if (colon_index >= 0 && colon_index < text_len) {
            /* Check again, starting from just after the colon */
            if (_check_for_legacy_statements(self, colon_index+1) < 0) {
                return -1;
            }
        }
    }
    return 0;
}

这意味着如果行中有任何左括号,它不会触发"Missing parenthesis"消息。即使左括号在评论中,这也会导致一般的 SyntaxError 消息:

print 10  # what(
    print 10  # what(
           ^
SyntaxError: invalid syntax

请注意,由白色 space 分隔的两个 names/variables 的光标位置始终是第二个姓名的结尾:

>>> 10 100
    10 100
         ^
SyntaxError: invalid syntax

>>> name1 name2
    name1 name2
              ^
SyntaxError: invalid syntax

>>> name1 name2([1, 2])
    name1 name2([1, 2])
              ^
SyntaxError: invalid syntax

难怪光标指向maxx,因为它是第二个名字的最后一个字符。第二个名字后面的所有内容(如 .([、...)都将被忽略,因为 Python 已经找到 SyntaxError,并且它不需要更进一步,因为没有什么可以使它成为有效的语法。

可能是我没看懂,但我不明白为什么Python要早点指出错误。 print 是一个常规函数,即一个引用函数的变量,所以这些都是有效的语句:

print(10)
print, max, 2
str(print)
print.__doc__
[print] + ['a', 'b']
{print: 2}

据我了解,解析器需要读取print(在本例中为max)之后的下一个完整标记,以确定是否存在语法错误。它不能只说 "fail if there is no open parenthesis",因为在 print 之后可能会有许多不同的标记,具体取决于当前上下文。

我不认为 print 后面可以直接跟另一个标识符或文字,所以你可以争辩说只要有一个字母、一个数字或引号你就应该停止,但这会混淆解析器和词法分析器的工作。

查看 source code for exceptions.c,就在 _set_legacy_print_statement_msg 上方,有一个不错的块评论:

/* To help with migration from Python 2, SyntaxError.__init__ applies some
 * heuristics to try to report a more meaningful exception when print and
 * exec are used like statements.
 *
 * The heuristics are currently expected to detect the following cases:
 *   - top level statement
 *   - statement in a nested suite
 *   - trailing section of a one line complex statement
 *
 * They're currently known not to trigger:
 *   - after a semi-colon
 *
 * The error message can be a bit odd in cases where the "arguments" are
 * completely illegal syntactically, but that isn't worth the hassle of
 * fixing.
 *
 * We also can't do anything about cases that are legal Python 3 syntax
 * but mean something entirely different from what they did in Python 2
 * (omitting the arguments entirely, printing items preceded by a unary plus
 * or minus, using the stream redirection syntax).
 */

这里有一些有趣的信息。另外,在同一个文件中的SyntaxError_init方法中,我们可以看到

    /*
     * Issue #21669: Custom error for 'print' & 'exec' as statements
     *
     * Only applies to SyntaxError instances, not to subclasses such
     * as TabError or IndentationError (see issue #31161)
     */
    if ((PyObject*)Py_TYPE(self) == PyExc_SyntaxError &&
            self->text && PyUnicode_Check(self->text) &&
            _report_missing_parentheses(self) < 0) {
        return -1;
    }

另请注意,以上引用 issue #21669 on the python bugtracker 以及作者和 Guido 之间关于如何着手进行的一些讨论。于是我们跟着文件最下面的兔子(也就是_report_missing_parentheses),看到...

legacy_check_result = _check_for_legacy_statements(self, 0);

然而,在某些情况下,这会被绕过并打印正常的 SyntaxError 消息,请参阅 了解更多信息。如果我们将一个函数执行到 _check_for_legacy_statements,我们 最终 会看到遗留打印语句的 实际检查

/* Check for legacy print statements */
if (print_prefix == NULL) {
    print_prefix = PyUnicode_InternFromString("print ");
    if (print_prefix == NULL) {
        return -1;
    }
}
if (PyUnicode_Tailmatch(self->text, print_prefix,
                        start, text_len, -1)) {

    return _set_legacy_print_statement_msg(self, start);
}

所以,为了回答这个问题:"Why isn't Python able to detect the problem earlier?",我想说括号的问题不是检测到的;它实际上是 语法错误之后被解析。这一直是一个语法错误,但后来发现了关于括号的实际小部分,只是为了提供额外的提示。

除了那些出色的答案之外,即使不查看源代码,我们也能猜到 print 特殊错误消息是一个杂凑:

所以:

print dfjdkf
           ^
SyntaxError: Missing parentheses in call to 'print'

但是:

>>> a = print
>>> a dsds
Traceback (most recent call last):
  File "<interactive input>", line 1
    a dsds
         ^
SyntaxError: invalid syntax

even if a == print 但在那个阶段,它还没有被评估,所以你得到的是通用的无效语法消息而不是被黑的 print 语法消息,这证明有一个kludge当第一个标记是 print.

如果需要,再提供一个证明:

>>> print = None
>>> print a
Traceback (most recent call last):
  File "C:\Python34\lib\code.py", line 63, in runsource
    print a
          ^
SyntaxError: Missing parentheses in call to 'print'

在那种情况下 print == None,但特定消息仍会出现。