Imprimer sans parenthèses messages d'erreur variables en utilisant Python 3

quand j'essaie d'utiliser print sans parenthèses sur un nom simple en Python 3.4 je reçois:

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

Ok, maintenant j'ai compris, j'ai juste oublié de porter mon code Python 2.

Mais maintenant, quand j'essaie d'imprimer le résultat d'une fonction:

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

ou:

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

(Notez que le curseur vers le caractère avant le premier point dans ce cas.)

le le message est différent (et légèrement trompeur, puisque le marqueur est en dessous de la fonction max ).

pourquoi Python n'est-il pas capable de détecter le problème plus tôt?

Note: cette question a été inspirée par la confusion autour de cette question: Pandas lire.erreur de syntaxe csv , où quelques experts de Python ont manqué le vrai problème à cause du message d'erreur trompeur.

28
demandé sur Peter Mortensen 2018-01-10 12:45:38

4 réponses

en regardant le code source pour exceptions.c , juste au-dessus de _set_legacy_print_statement_msg il y a ce joli commentaire de bloc:

/* 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).
 */

donc il y a des infos intéressantes. En outre, dans la méthode SyntaxError_init dans le même fichier, nous pouvons voir

    /*
     * 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;
    }

Notez aussi que les références ci-dessus numéro # 21669 sur le bugtracker python avec quelques discussions entre l'auteur et Guido sur comment aller à ce sujet. Nous suivons donc le lapin (c'est-à-dire _report_missing_parentheses ) qui est tout en bas du dossier, et voir...

legacy_check_result = _check_for_legacy_statements(self, 0);

cependant, il y a des cas où cela est contourné et le message normal SyntaxError est imprimé, voir réponse de MSeifert pour plus d'informations à ce sujet. Si nous allons d'une fonction jusqu'à _check_for_legacy_statements nous enfin voir le contrôle réel pour les déclarations d'impression héritées.

/* 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);
}

donc, pour répondre à la question: "Pourquoi Python n'est-il pas capable de détecter le problème plus tôt?", Je dirais que le problème avec les parenthèses n'est pas ce qui est détecté; il est en fait interprété après l'erreur de syntaxe. C'est une erreur de syntaxe tout le temps, mais l'article mineur réel sur les parenthèses est attrapé par la suite juste pour donner un indice supplémentaire.

28
répondu Alexander Reynolds 2018-01-10 11:07:42

le message d'exception spécial pour print utilisé comme déclaration au lieu de fonction est en fait mis en œuvre comme un cas spécial .

en gros, quand un SyntaxError est créé, il appelle une fonction spéciale qui vérifie pour un print déclaration basée sur la ligne l'exception se réfère.

toutefois, le premier test dans ce fonction (celle qui est responsable du message d'erreur "parenthèse manquante") est s'il y a une parenthèse d'ouverture dans la ligne. J'ai copié le code source de cette fonction (CPython 3.6.4) et j'ai marqué les lignes correspondantes avec "flèches":

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;
}

cela signifie qu'il ne déclenchera pas le message" parenthèse manquante "s'il y a n'importe quelle parenthèse d'ouverture dans la ligne. Cela conduit au message général SyntaxError même si l'ouverture la parenthèse est dans un commentaire:

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

notez que la position du curseur pour deux noms/variables séparés par un espace blanc est toujours la fin du second nom:

>>> 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

il n'est donc pas étonnant que le curseur pointe vers le x de max , car c'est le dernier caractère du second nom. Tout ce qui suit le second nom (comme . , ( , [ , ...) être ignoré, parce que Python a déjà trouvé un SyntaxError , et il n'a pas besoin d'aller plus loin, parce que rien ne pourrait le rendre syntaxe valide.

17
répondu MSeifert 2018-01-10 21:08:14

peut-être que je ne comprends pas quelque chose, mais je ne vois pas pourquoi Python devrait signaler l'erreur plus tôt. print est une fonction régulière, c'est-à-dire une variable référençant une fonction, donc ce sont tous des énoncés valides:

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

si je comprends bien, l'analyseur doit lire le prochain jeton complet après print ( max dans ce cas-ci) afin de déterminer s'il y a une erreur de syntaxe. Elle ne peut pas simplement dire "échouer s'il n'y a pas d'ouverture". parenthèse", parce qu'il y a un certain nombre de tokens différents qui peuvent aller après print selon le contexte actuel.

Je ne pense pas qu'il y ait un cas où print puisse être suivi directement par un autre identifiant ou un littéral, donc vous pourriez argumenter que dès qu'il y a une lettre, un nombre ou des citations vous devriez arrêter, mais ce serait mélanger le travail de l'analyseur et du lexer.

4
répondu jdehesa 2018-01-10 21:09:49

dans les ajouts à ces excellentes réponses, sans même regarder le code source, nous aurions pu deviner que le message d'erreur spécial print était un kludge:

:

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

mais:

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

même si a == print mais à ce stade, il n'est pas encore évalué, donc vous obtenez le message de syntaxe générique invalide au lieu du message de syntaxe piraté print , ce qui prouve qu'il y a un kludge lorsque le premier jeton est print .

une autre preuve si nécessaire:

>>> 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'

dans ce cas print == None , mais le message spécifique apparaît toujours.

4
répondu Jean-François Fabre 2018-01-10 21:27:49