web-dev-qa-db-fra.com

Impression sans parenthèses variant le message d'erreur en utilisant Python 3

Lorsque j'essaie d'utiliser print sans parenthèses sur un nom simple dans Python 3.4, j'obtiens:

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

Ok, maintenant je comprends, 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 pointe vers le caractère avant le premier point dans ce cas.)

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

Pourquoi Python n'est-il pas en mesure de détecter le problème plus tôt?

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

30

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

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

Il y a donc des informations intéressantes. De plus, dans le SyntaxError_init méthode dans le même fichier, on peut 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 également que les références ci-dessus problème # 21669 sur le python bugtracker avec une discussion entre l'auteur et Guido sur la façon de procéder. Nous suivons donc le lapin ( C'est, _report_missing_parentheses) qui se trouve tout en bas du fichier, et voyez ...

legacy_check_result = _check_for_legacy_statements(self, 0);

Cependant, dans certains cas, cela est ignoré et le message normal SyntaxError est imprimé, voir réponse de MSeifert pour plus d'informations. Si nous allons une fonction jusqu'à _check_for_legacy_statements nous enfin voyons le contrôle réel des instructions 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 ne peut-il pas détecter le problème plus tôt?", Je dirais que le problème entre parenthèses n'est pas ce qui est détecté; il est en fait analysé après l'erreur de syntaxe. C'est une erreur de syntaxe tout le temps, mais la partie mineure réelle sur les parenthèses est interceptée par la suite juste pour donner un indice supplémentaire.

28
alkasm

Le message d'exception spécial pour print utilisé comme instruction au lieu de comme fonction est en fait implémenté comme un cas spécial.

En gros, quand un SyntaxError est créé, il appelle une fonction spéciale qui vérifie une print instruction basée sur la ligne l'exception fait référence à.

Cependant, le premier test dans cette fonction (celle responsable du message d'erreur "Parenthèse manquante") est s'il y a une parenthèse ouvrante dans la ligne. J'ai copié le code source de cette fonction (CPython 3.6.4) et j'ai marqué les lignes pertinentes avec des "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 une parenthèse ouvrante dans la ligne. Cela conduit au message général SyntaxError même si la parenthèse ouvrante 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 deuxième 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 deuxième nom. Tout ce qui suit le deuxième nom (comme ., (, [, ...) est ignoré, car Python a déjà trouvé un SyntaxError, et il n'a pas besoin d'aller plus loin, car rien ne pourrait rendre la syntaxe valide.

17
MSeifert

en plus de 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:

donc:

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é, vous obtenez donc le message de syntaxe générique invalide au lieu du message de syntaxe print piraté, ce qui prouve qu'il y a un kludge lorsque le premier jeton est print.

une autre preuve si besoin:

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

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 , ce sont donc toutes des déclarations 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) afin de déterminer s'il y a une erreur de syntaxe. Il ne peut pas simplement dire "échouer s'il n'y a pas de parenthèses ouvertes", car il existe un certain nombre de jetons 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 directement suivi d'un autre identifiant ou d'un littéral, donc vous pourriez faire valoir que dès qu'il y a une lettre, un nombre ou des guillemets, vous devez arrêter, mais que serait de mélanger le travail du parseur et du lexeur.

4
jdehesa