¿Por qué Python 3 permite "00" como literal para 0 pero no permite "01" como literal para 1?

111

¿Por qué Python 3 permite "00" como literal para 0 pero no permite "01" como literal para 1? ¿Existe una buena razón? Esta inconsistencia me desconcierta. (Y estamos hablando de Python 3, que deliberadamente rompió la compatibilidad con versiones anteriores para lograr objetivos como la coherencia).

Por ejemplo:

>>> from datetime import time
>>> time(16, 00)
datetime.time(16, 0)
>>> time(16, 01)
  File "<stdin>", line 1
    time(16, 01)
              ^
SyntaxError: invalid token
>>>
morsa
fuente
42
¡No se puede eliminar ahora o romperá la compatibilidad con esta pregunta!
John La Rooy

Respuestas:

103

Según https://docs.python.org/3/reference/lexical_analysis.html#integer-literals :

Los literales enteros se describen mediante las siguientes definiciones léxicas:

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"

No hay límite para la longitud de los literales enteros aparte de lo que se puede almacenar en la memoria disponible.

Tenga en cuenta que no se permiten ceros a la izquierda en un número decimal distinto de cero. Esto es para la desambiguación con literales octales de estilo C, que Python usaba antes de la versión 3.0.

Como se indica aquí, no se permiten ceros a la izquierda en un número decimal distinto de cero . "0"+es legal como un caso muy especial, que no estaba presente en Python 2 :

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

SVN commit r55866 implementó PEP 3127 en el tokenizador, que prohíbe los 0<octal>números antiguos . Sin embargo, curiosamente, también agrega esta nota:

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

con una nonzerobandera especial que solo arroja un SyntaxErrorsi la siguiente secuencia de dígitos contiene un dígito distinto de cero.

Esto es extraño porque PEP 3127 no permite este caso:

Este PEP propone que la capacidad de especificar un número octal mediante el uso de un cero a la izquierda se eliminará del lenguaje en Python 3.0 (y el modo de vista previa de Python 3.0 de 2.6), y que se generará un SyntaxError siempre que un "0" a la izquierda sea seguido inmediatamente por otro dígito .

(énfasis mío)

Entonces, el hecho de que se permitan múltiples ceros está violando técnicamente el PEP, y fue básicamente implementado como un caso especial por Georg Brandl. Hizo el correspondiente cambio de documentación para señalar que "0"+era un caso válido para decimalinteger(anteriormente que se había cubierto en octinteger).

Probablemente nunca sabremos exactamente por qué Georg eligió hacer "0"+válido: puede seguir siendo para siempre un caso extraño en Python.


ACTUALIZACIÓN [28 de julio de 2015]: Esta pregunta dio lugar a un animado hilo de discusión sobre las ideas de Python en el que Georg intervino :

Steven D'Aprano escribió:

¿Por qué se definió de esa manera? [...] ¿Por qué escribiríamos 0000 para obtener cero?

Podría decírtelo, pero luego tendría que matarte.

Georg

Más tarde, el hilo generó este informe de error con el objetivo de deshacerse de este caso especial. Aquí, Georg dice :

No recuerdo la razón de este cambio deliberado (como se ve en el cambio de documentos).

No puedo encontrar una buena razón para este cambio ahora [...]

y así lo tenemos: la razón precisa detrás de esta inconsistencia se pierde en el tiempo.

Finalmente, tenga en cuenta que el informe de error fue rechazado: los ceros iniciales seguirán siendo aceptados solo en números enteros cero para el resto de Python 3.x.

neonneo
fuente
6
¿Por qué dices "Probablemente nunca sabremos exactamente por qué Georg decidió ..."? Si alguien que lo conoce ve este hilo y le informa al respecto, ¡entonces podría venir a dar su respuesta! (a menos que sepa que siempre se niega a discutir su trabajo anterior en Python, o alguna circunstancia similar)
morsa
1
No entiendo por qué no solo hicieron el segundo octintegercaso de Python 2 "0" octdigit*. 0es un literal octal en C / C ++.
Random832
1
En realidad, el inglés es un poco ambiguo a este respecto. La palabra "otro" puede significar "uno más" o puede significar "uno diferente". Una interpretación válida en inglés de la cita en negrita de PEP 3127 es que significa "se generará un error de sintaxis cada vez que un '0' inicial sea seguido inmediatamente por un dígito que no sea '0'" No estoy seguro de si eso es lo que realmente se pretendía ( aunque esa interpretación parece estar respaldada por el código real), pero en cualquier caso, no creo que sea correcto decir que la PEP se viola técnicamente sin una aclaración adicional de esa oración.
GrandOpener
2
@GrandOpener: Tenga en cuenta que 001es ilegal, mientras que su interpretación lo haría legal (ya que el significado de "inmediatamente" debe ser bastante inequívoco).
nneonneo
Buen punto. Así que definitivamente se viola la PEP; lo que es ambiguo es la naturaleza exacta en la que se viola. :)
GrandOpener
17

Es un caso especial ( "0"+)

2.4.4. Literales enteros

Los literales enteros se describen mediante las siguientes definiciones léxicas:

integer :: = decimalinteger | octinteger | hexinteger | bininteger
decimalinteger :: = dígito que no es de cero dígitos * | "0" +
nonzerodigit :: = "1" ... "9"
dígito :: = "0" ... "9"
octinteger :: = "0" ("o" | "O") octdigit +
hexinteger :: = "0" ("x" | "X") hexdigit +
bininteger :: = "0" ("b" | "B") bindigit +
octdigit :: = "0" ... "7"
hexdigit :: = dígito | "a" ... "f" | "A" ... "F"
bindigit :: = "0" | "1"

Si observa la gramática, es fácil ver que 0necesita un caso especial. Sin +embargo, no estoy seguro de por qué el " " se considera necesario allí. Es hora de buscar en la lista de correo de desarrolladores ...


Es interesante notar que en Python2, más de uno 0fue analizado como un octinteger( 0aunque el resultado final sigue siendo )

decimalinteger :: = dígito que no es de cero dígitos * | "0"
octinteger :: = "0" ("o" | "O") octdigit + | "0" octdigit +
John La Rooy
fuente
1
¿Y alguna idea de por qué hay "0"+y no "0"?
lejlot
1
@lejlot, todavía no, pero estoy intrigado. Sin embargo, definitivamente es parte de la especificación
John La Rooy
3

Python2 usó el cero a la izquierda para especificar números octales:

>>> 010
8

Para evitar este comportamiento (? Engañosa), python3 requiere prefijos explícitas 0b, 0o, 0x:

>>> 0o10
8
dlask
fuente
15
La pregunta sigue siendo: ¿por qué está 00permitido? (Y 000, 0000etc.)
Michael Geary
4
@MichaelGeary: posiblemente porque no puede ser ambiguo (00000000 es 0 independientemente de la base) y eliminarlo rompería el código innecesariamente. Todavía extraño.
RemcoGerlich
5
@RemcoGerlich Si no me equivoco, 01también lo es 1independientemente de la base.
Holt
2
@Holt: pero ¿permitir "0" + "1"? como caso especial, probablemente sería aún más confuso.
RemcoGerlich
4
@RemcoGerlich Nunca dije que no lo haría;) Solo estaba diciendo que can't be ambiguousno es un argumento ya 01que tampoco puede ser ambiguo. En mi opinión, el 00caso es solo un caso especial porque es 0lo que no debería ser.
Holt