使用Python 3打印没有括号的错误消息

Jea*_*bre 30 python syntax-error python-3.x python-internals

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

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

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

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

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

要么:

print max.__call__(23)
        ^
SyntaxError: invalid syntax
Run Code Online (Sandbox Code Playgroud)

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

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

为什么Python不能提前检测到问题?

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

alk*_*asm 28

查看源代码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).
 */
Run Code Online (Sandbox Code Playgroud)

所以有一些有趣的信息.另外,在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;
    }
Run Code Online (Sandbox Code Playgroud)

另请注意,上面的参考文献在python bugtracker上发布#21669,作者和Guido之间就如何解决这个问题进行了一些讨论.所以我们跟着位于_report_missing_parentheses文件底部的兔子(即),看看...

legacy_check_result = _check_for_legacy_statements(self, 0);
Run Code Online (Sandbox Code Playgroud)

但是,在某些情况下会绕过它并SyntaxError打印正常消息,请参阅MSeifert的答案以获取更多相关信息.如果我们执行一个函数,_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);
}
Run Code Online (Sandbox Code Playgroud)

因此,回答这个问题:"为什么Python不能更早地检测到问题?",我会说括号中的问题不是检测到的问题; 它实际上是语法错误之后解析的.这是一个语法错误,但是关于括号的实际小部分之后被捕获只是为了给出一个额外的提示.

  • 但它甚至没有进入上述案例中的`_check_for_legacy_statements`,因为甚至更早的检查`_report_missing_parentheses`检查任何开放的parens,如果它发现它已经从该函数返回(参见我的答案). (2认同)

MSe*_*ert 17

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

粗略地说,在SyntaxError创建a 时,它会调用一个特殊函数,该函数根据异常引用的检查print 语句.

但是,此函数中的第一个测试(负责"缺失括号"错误消息的那个)是行中是否有任何左括号.我复制了该函数的源代码(CPython 3.6.4),并用"箭头"标记了相关的行:

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;
}
Run Code Online (Sandbox Code Playgroud)

这意味着如果行中有任何左括号,它将不会触发"缺失括号"消息.SyntaxError即使左括号在注释中,这也会导致一般性消息:

print 10  # what(
    print 10  # what(
           ^
SyntaxError: invalid syntax
Run Code Online (Sandbox Code Playgroud)

请注意,由空格分隔的两个名称/变量的光标位置始终是第二个名称的末尾:

>>> 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
Run Code Online (Sandbox Code Playgroud)

所以毫无疑问光标指向的xmax,因为它是第二个名字中的最后一个字符.接下来的第二个名字一切(如.,(,[,...)被忽略,因为Python已经找到了SyntaxError,它并不需要更进一步,因为没有什么可以使它有效的语法.


jde*_*esa 5

也许我不明白什么,但我不明白为什么 Python 应该更早地指出错误。print是常规函数,即引用函数的变量,因此这些都是有效的语句:

print(10)
print, max, 2
str(print)
print.__doc__
[print] + ['a', 'b']
{print: 2}
Run Code Online (Sandbox Code Playgroud)

据我了解,解析器需要读取printmax在这种情况下)之后的下一个完整标记,以确定是否存在语法错误。它不能只说“如果没有开括号就失败”,因为print根据当前上下文,可能会出现许多不同的标记。

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


Jea*_*bre 5

除了这些出色的答案之外,甚至不看源代码,我们就可以猜到print特殊的错误消息是杂乱无章的:

所以:

print dfjdkf
           ^
SyntaxError: Missing parentheses in call to 'print'
Run Code Online (Sandbox Code Playgroud)

但:

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

即使a == print在那个阶段,它还没有被评估,所以你会得到通用的无效语法消息而不是被黑的print语法消息,这证明当第一个标记是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'
Run Code Online (Sandbox Code Playgroud)

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