Al ejecutar la siguiente línea:
>>> [0xfor x in (1, 2, 3)]Esperaba que Python devolviera un error.
En su lugar, el REPL devuelve:
[15]
¿Cuál puede ser la razón?
Python lee la expresión como [0xf or (x in (1, 2, 3))] , porque:
Nunca genera NameError gracias a la evaluación de cortocircuito : si la expresión que le queda al operador or es un valor real, Python nunca intentará evaluar el lado derecho.
Primero, tenemos que entender cómo Python lee los números hexadecimales.
En la enorme función tok_get de tokenizer.c , nosotros:
0x . El token analizado, 0xf (ya que "o" no está en el rango de 0-f), finalmente pasará al analizador PEG, que lo convertirá al valor decimal 15 (consulte el Apéndice A).
Todavía tenemos que analizar el resto del código, or x in (1, 2, 3)] , que queda como con el siguiente código:
[15 or x in (1, 2, 3)] Debido a que in tiene mayorprecedencia de operadores que or , podríamos esperar que x in (1, 2, 3) se evalúe primero.
Esa es una situación problemática, ya que x no existe y generará un NameError .
or es perezoso Afortunadamente, Python admite la evaluación de cortocircuito como or es un operador perezoso: si el operando de la izquierda es equivalente a True , Python no se molestará en evaluar el operando de la derecha.
Podemos verlo usando el módulo ast :
parsed = ast.parse('0xfor x in (1, 2, 3)', mode='eval') ast.dump(parsed)Producción:
Expression( body=BoolOp( op=Or(), values=[ Constant(value=15), # <-- Truthy value, so the next operand won't be evaluated. Compare( left=Name(id='x', ctx=Load()), ops=[In()], comparators=[ Tuple(elts=[Constant(value=1), Constant(value=2), Constant(value=3)], ctx=Load()) ] ) ] ) ) Entonces la expresión final es igual a [15] .
En la función parsenumber_raw de parsenumber_raw , podemos encontrar cómo Python trata los ceros iniciales:
if (s[0] == '0') { x = (long)PyOS_strtoul(s, (char **)&end, 0); if (x < 0 && errno == 0) { return PyLong_FromString(s, (char **)0, 0); } } PyOS_strtoul está en Python/mystrtoul.c .
Dentro de mystrtoul.c, el analizador analiza un carácter después de 0x . Si es un carácter hexadecimal, Python establece que la base del número sea 16:
if (*str == 'x' || *str == 'X') { /* there must be at least one digit after 0x */ if (_PyLong_DigitValue[Py_CHARMASK(str[1])] >= 16) { if (ptr) *ptr = (char *)str; return 0; } ++str; base = 16; } ...Luego analiza el resto del número siempre que los caracteres estén en el rango de 0-f:
while ((c = _PyLong_DigitValue[Py_CHARMASK(*str)]) < base) { if (ovlimit > 0) /* no overflow check required */ result = result * base + c; ... ++str; --ovlimit; }Eventualmente , establece el puntero para señalar el último carácter que se escaneó, que es un carácter más allá del último carácter hexadecimal:
if (ptr) *ptr = (char *)str;Como han explicado otros, es solo el número hexadecimal 0xf seguido del operador or . Los operadores generalmente no necesitan espacios circundantes, a menos que sea necesario para evitar la ambigüedad. En este caso, la letra o no puede formar parte de un número hexadecimal, por lo que no hay ambigüedad. Consulte la sección sobre espacios en blanco en la referencia del lenguaje Python.
El resto de la línea no se evalúa debido a la evaluación de cortocircuito, aunque, por supuesto, se analiza y compila.
Usando ese mismo "truco", puede escribir un código de Python ofuscado de manera similar que no arroja excepciones, por ejemplo:
>>> 0xbin b'in' False >>> 0xbis 1000 False >>> 0b1and 0b1is 0b00 False >>> 0o1if 0b1else Oy1then 1Otras respuestas ya dicen qué sucede exactamente. Pero para mí, la parte interesante fue que el operador se reconoce incluso sin espacios en blanco entre el número y este. En realidad, mi primer pensamiento fue "Wow, Python tiene un analizador extraño".
Pero antes de juzgar con demasiada dureza, tal vez debería preguntarles a mis otros amigos qué piensan:
Perl:
$ perl -le 'print(0xfor 3)' 15Lúa:
$ lua5.3 -e 'print(0xfor 4)' 15 Awk no tiene or , pero tiene in :
$ awk 'BEGIN { a[15]=1; print(0x0fin a); }' 1¿Rubí? (Realmente no lo sé, pero supongamos):
$ ruby -e 'puts 0x0for 5' 15Sí, FWIW, Python no está solo, todos esos otros lenguajes de tipo script también reconocen los operadores alfabéticos incluso si se pegan inmediatamente al reverso de una constante numérica.