Pourquoi Python 3 autorise-t-il «00» comme littéral pour 0 mais pas «01» comme littéral pour 1?

111

Pourquoi Python 3 autorise-t-il "00" comme littéral pour 0 mais pas "01" comme littéral pour 1? Y a-t-il une bonne raison? Cette incohérence me déroute. (Et nous parlons de Python 3, qui a délibérément brisé la compatibilité descendante afin d'atteindre des objectifs tels que la cohérence.)

Par exemple:

>>> from datetime import time
>>> time(16, 00)
datetime.time(16, 0)
>>> time(16, 01)
  File "<stdin>", line 1
    time(16, 01)
              ^
SyntaxError: invalid token
>>>
morse
la source
42
Il ne peut pas être supprimé maintenant, ou il rompra la rétrocompatibilité avec cette question!
John La Rooy

Réponses:

103

Par https://docs.python.org/3/reference/lexical_analysis.html#integer-literals :

Les littéraux entiers sont décrits par les définitions lexicales suivantes:

integer        ::=  decimalinteger | octinteger | hexinteger | bininteger
decimalinteger ::=  nonzerodigit digit* | "0"+
nonzerodigit   ::=  "1"..."9"
digit          ::=  "0"..."9"
octinteger     ::=  "0" ("o" | "O") octdigit+
hexinteger     ::=  "0" ("x" | "X") hexdigit+
bininteger     ::=  "0" ("b" | "B") bindigit+
octdigit       ::=  "0"..."7"
hexdigit       ::=  digit | "a"..."f" | "A"..."F"
bindigit       ::=  "0" | "1"

Il n'y a pas de limite pour la longueur des littéraux entiers en dehors de ce qui peut être stocké dans la mémoire disponible.

Notez que les zéros non significatifs dans un nombre décimal différent de zéro ne sont pas autorisés. Ceci est destiné à la clarification des littéraux octaux de style C, que Python utilisait avant la version 3.0.

Comme indiqué ici, les zéros non significatifs dans un nombre décimal différent de zéro ne sont pas autorisés. "0"+est légal en tant que cas très spécial, qui n'était pas présent dans Python 2 :

integer        ::=  decimalinteger | octinteger | hexinteger | bininteger
decimalinteger ::=  nonzerodigit digit* | "0"
octinteger     ::=  "0" ("o" | "O") octdigit+ | "0" octdigit+

SVN commit r55866 a implémenté PEP 3127 dans le tokenizer, qui interdit les anciens 0<octal>numéros. Cependant, curieusement, il ajoute également cette note:

/* in any case, allow '0' as a literal */

avec un nonzeroindicateur spécial qui ne lance un que SyntaxErrorsi la séquence de chiffres suivante contient un chiffre différent de zéro.

C'est étrange car la PEP 3127 ne permet pas ce cas:

Ce PEP propose que la possibilité de spécifier un nombre octal en utilisant un zéro non significatif sera supprimée du langage en Python 3.0 (et le mode de prévisualisation Python 3.0 de 2.6), et qu'une SyntaxError sera levée chaque fois qu'un "0" non significatif est immédiatement suivi d'un autre chiffre .

(c'est moi qui souligne)

Donc, le fait que plusieurs zéros soient autorisés viole techniquement le PEP, et a été fondamentalement implémenté comme un cas particulier par Georg Brandl. Il a fait le changement de documentation correspondant pour noter que "0"+c'était un cas valable pour decimalinteger(auparavant, cela était couvert par octinteger).

Nous ne saurons probablement jamais exactement pourquoi Georg a choisi de rendre "0"+valide - cela peut rester à jamais un cas de coin étrange en Python.


MISE À JOUR [28 juillet 2015]: Cette question a conduit à un fil de discussion animé sur les idées python dans lequel Georg est intervenu :

Steven D'Aprano a écrit:

Pourquoi a-t-il été défini de cette façon? [...] Pourquoi écririons-nous 0000 pour obtenir zéro?

Je pourrais te le dire, mais ensuite je devrais te tuer.

Georg

Plus tard, le fil a engendré ce rapport de bogue visant à se débarrasser de ce cas particulier. Ici, Georg dit :

Je ne me souviens pas de la raison de ce changement délibéré (comme le montre le changement de la documentation).

Je ne parviens pas à trouver une bonne raison pour ce changement maintenant [...]

et ainsi nous l'avons: la raison précise de cette incohérence est perdue dans le temps.

Enfin, notez que le rapport de bogue a été rejeté: les zéros non significatifs continueront à être acceptés uniquement sur les entiers zéro pour le reste de Python 3.x.

nneonneo
la source
6
Pourquoi dites-vous "Nous ne saurons probablement jamais exactement pourquoi Georg a choisi de ..."? Si quelqu'un qui le connaît voit ce fil et l'informe, alors il pourrait venir donner sa réponse! (à moins que vous ne sachiez qu'il refuse pour toujours de discuter de son travail passé sur Python, ou d'une circonstance similaire)
morse
1
Je ne comprends pas pourquoi ils n'ont pas simplement fait le deuxième octintegercas Python 2 "0" octdigit*. 0est un littéral octal en C / C ++.
Random832
1
En fait, l'anglais est un peu ambigu à cet égard. Le mot «un autre» peut signifier «un de plus» ou «un autre». Une interprétation anglaise valide de la citation en gras de la PEP 3127 est de signifier "une SyntaxError sera déclenchée chaque fois qu'un '0' de début est immédiatement suivi d'un chiffre autre que '0'" Je ne suis pas sûr si c'est ce qui était réellement prévu ( bien que cette interprétation semble être soutenue par le code réel), mais en tout cas, je ne pense pas qu'il soit exact de dire que le PEP est techniquement violé sans clarification supplémentaire de cette phrase.
GrandOpener
2
@GrandOpener: Notez que 001c'est illégal, alors que votre interprétation rendrait cela légal (puisque le sens de "immédiatement" devrait être assez clair).
nneonneo
Bon point. Le PEP est donc définitivement violé; ce qui est ambigu, c'est la nature exacte dans laquelle il est violé. :)
GrandOpener
17

C'est un cas particulier ( "0"+)

2.4.4. Entiers littéraux

Les littéraux entiers sont décrits par les définitions lexicales suivantes:

entier :: = nombre décimal | octinteger | hexinteger | bininteger
decimalinteger :: = chiffre non zérodigit * | "0" +
nonzerodigit :: = "1" ... "9"
chiffre :: = "0" ... "9"
octinteger :: = "0" ("o" | "O") octdigit +
hexinteger :: = "0" ("x" | "X") hexdigit +
bininteger :: = "0" ("b" | "B") bindigit +
octdigit :: = "0" ... "7"
hexdigit :: = chiffre | "un" ... "f" | "UN F"
bindigit :: = "0" | "1"

Si vous regardez la grammaire, il est facile de voir que cela 0nécessite un cas particulier. Je ne sais pas pourquoi le « +» est considéré comme nécessaire ici. Il est temps de parcourir la liste de diffusion des développeurs ...


Il est intéressant de noter que dans Python2, plus d'un a 0été analysé comme un octinteger(le résultat final est toujours 0cependant)

decimalinteger :: = chiffre non zérodigit * | "0"
octinteger :: = "0" ("o" | "O") octdigit + | "0" octdigit +
John La Rooy
la source
1
Et une idée pourquoi il y en a "0"+et non "0"?
lejlot
1
@lejlot, pas encore - mais je suis intrigué. Cela fait certainement partie de la spécification
John La Rooy
3

Python2 a utilisé le zéro non significatif pour spécifier les nombres octaux:

>>> 010
8

Pour éviter ce comportement (? Induire en erreur), python3 exige préfixes explicites 0b, 0o, 0x:

>>> 0o10
8
dlask
la source
15
La question demeure: pourquoi est 00autorisé? (Et 000, 0000etc.)
Michael Geary
4
@MichaelGeary: peut-être parce qu'il ne peut pas être ambigu (00000000 est égal à 0 quelle que soit la base) et que sa suppression briserait inutilement le code? Encore étrange.
RemcoGerlich
5
@RemcoGerlich Si je ne me trompe pas, 01c'est aussi 1indépendamment de la base.
Holt
2
@Holt: mais autorisant "0" + "1"? un cas particulier serait probablement encore plus déroutant.
RemcoGerlich
4
@RemcoGerlich Je n'ai jamais dit que ce ne serait pas le cas;) Je disais simplement que ce can't be ambiguousn'est pas un argument car 01il ne peut pas non plus être ambigu. OMI, le 00cas n'est qu'un cas particulier car c'est 0ce qui ne devrait pas l'être.
Holt