¿Python prohíbe dos identificadores Unicode de aspecto similar?

81

Estaba jugando con identificadores Unicode y me encontré con esto:

>>> 𝑓, x = 1, 2
>>> 𝑓, x
(1, 2)
>>> 𝑓, f = 1, 2
>>> 𝑓, f
(2, 2)

¿Que está pasando aqui? ¿Por qué Python reemplaza el objeto al que se hace referencia 𝑓, pero solo a veces? ¿Dónde se describe ese comportamiento?

Erik Cederstrand
fuente
9
Esta es una pregunta interesante, pero su ejemplo mínimo reproducible podría haber sido𝑓=1 f=2 print(𝑓)
khelwood
1
Gracias. Hizo el ejemplo aún más pequeño ahora.
Erik Cederstrand
37
obxkcd
Barmar
1
a, a = 1, 2; a, a. Esto no tiene nada que ver con fo 𝑓.
user76284
4
El ejemplo 𝑓 = 3; fbastaría.
user76284

Respuestas:

81

PEP 3131 - Admite identificadores no ASCII dice

Todos los identificadores se convierten a la forma normal NFKC durante el análisis; la comparación de identificadores se basa en NFKC.

Puede utilizar unicodedatapara probar las conversiones:

import unicodedata

unicodedata.normalize('NFKC', '𝑓')
# f

lo que indicaría que '𝑓'se convierte 'f'en al analizar. Conduciendo a lo esperado:

𝑓  = "Some String"
print(f)
# "Some String"
Mark M
fuente
23
Esta es una gran respuesta, pero una decisión terrible por parte de los desarrolladores centrales de Python. Observo que en la discusión de este PEP, una de las objeciones fue que Unicode no se entiende bien y tiene herramientas débiles. Ahora, más de una década después, me pregunto si es hora de reconsiderar la romanización de los identificadores Unicode.
Adam Smith
33
@AdamSmith, pero la normalización Unicode no es romanización. Puede tener πcomo un identificador de Python que sea distinto de pmuy bien. Si lo entiendo correctamente, el plegado NFK * se trata de caracteres que la gente de Unicode pensó que deberían haber sido el mismo carácter para empezar, pero no se pueden fusionar debido a la compatibilidad con versiones anteriores con algunas codificaciones heredadas.
lenz
19
Hay dos tipos de equivalencia de caracteres: canónica y compatibilidad. La equivalencia canónica debería representar exactamente el mismo glifo, que no es el caso entre 𝑓 y f. NFKC normaliza las equivalencias tanto canónicas como de compatibilidad, lo que estoy de acuerdo es una mala elección para un lenguaje de programación como Python, que diferencia entre mayúsculas y minúsculas: se espera que los identificadores que se representan de manera diferente sean diferentes. Python debería haber usado NFC, lo que asegura que 𝑓 yf sean cosas diferentes.
lvella
27
Se necesita alguna forma de normalización debido, por ejemplo, a los caracteres latinos con diacríticos; si veo un carácter como 'ü', entonces podría ser un carácter compuesto (u + diéresis combinada) o un carácter único precompuesto; el usuario no tendría una forma o deseo razonable de distinguirlos, y su método de entrada preferido probablemente le permitiría ingresar solo una de estas opciones. Por lo tanto, es deseable que si veo 'ü' y escribo 'ü', el idioma considere los caracteres como equivalentes incluso si están codificados de manera diferente, aunque la normalización NFC probablemente sea suficiente para eso.
Peteris
8
Python admite Unicode para identificadores con el fin de facilitar su uso en la definición de identificadores en idiomas distintos del inglés, no para proporcionar el mismo acceso a todos los puntos de código Unicode. Por ejemplo, actualmente es bastante difícil piratear el analizador para que admita operadores Unicode, porque primero se supone que cualquier carácter que no sea ASCII es parte de un identificador, incluso si el carácter Unicode en cuestión no es una parte válida de un identificador. La idea no es admitir la extracción de Unicode para caracteres "interesantes", sino admitir caracteres producidos por diseños de teclado estándar que no estén en inglés.
chepner
28

Aquí hay un pequeño ejemplo, solo para mostrar lo horrible que es esta "función":

𝕋𝐡ᵢ𝔰_f𝔢𝘢𝚝𝓊ᵣₑ_𝕤ₕ𝔬𝔲𝖑𝔡_dₑ𝕗ᵢ𝘯i𝘵𝚎ℓy_𝒷𝘦_𝐚_𝚋ᵘg = 42
print(T𝗵ℹ𝚜_𝒇e𝖆𝚝𝙪ᵣe_ₛ𝔥º𝓾𝗹𝙙_𝚍e𝒇ᵢ𝒏ⁱtᵉ𝕝𝘆_𝖻ℯ_𝔞_𝖇𝖚𝓰)
# => 42

¡Pruébelo en línea! (Pero por favor no lo uses)

Y como lo menciona @MarkMeyer, dos identificadores pueden ser distintos aunque tengan el mismo aspecto ("LETRA A MAYÚSCULA CIRÍLICA" y "LETRA A CAPITAL LATINA")

А = 42
print(A)
# => NameError: name 'A' is not defined
Eric Duminil
fuente
3
¿Me dan ganas de escribir un equivalente de jsfuck.com ... python-unicode-hell.com?
Mathieu VIALES
2
@MathieuVIALES 𝓕𝕖𝒆𝑙 𝐟ʳ𝙚ₑ ᵗ𝗈 ᵈ𝚘 𝓈º. Yo 𝐡a𝔳ᵉ 𝒔𝚘𝙢𝖾 𝒄𝑜𝖽ᵉ 𝖑𝒶𝒚𝑖𝒏𝕘 arₒ𝘶𝘯𝖽. 𝐈 ʷ𝙖n𝓉ℯ𝙙 𝒕𝘰 𝗍𝕣o𝑙𝗅 ⅽ𝔬𝚕𝘭ᵉ𝗮𝓰𝘶𝖊𝔰 ʷ𝚒ₜ𝙝 𝓲ᵗ, 𝕓𝒖t 𝚝ℎₑ 𝗋𝑒𝙨𝓊𝕝𝓉 ⅈ𝔰 𝓳ᵘ𝑠𝙩 t𝚘𝗈 𝗵o𝒓𝑟ible 𝘀𝐨 𝐼 ⁿ𝚎v𝖾𝔯 ᵘ𝓼ₑⅾ ⅈt. 𝕌𝓃𝗍𝚒l 𝕟𝚘𝙬.
Eric Duminil
8
Y luego, por supuesto: А = 42; print(A)-> "NameError: el nombre 'A' no está definido"
Mark M
8
El objetivo nunca fue abrir la puerta a nombres de identificadores arbitrariamente complejos, sino facilitar la escritura de identificadores en el idioma nativo del programador (utilizando una distribución de teclado nativa de ese idioma). Es mejor seguir la clasificación de Unicode de un punto de código como una letra que actuar como árbitro para el cual los sistemas de escritura pueden y no pueden usarse para identificadores. (Y limitar un identificador a caracteres de un solo sistema de escritura está mucho más allá de la calificación de pago del analizador)
Chepner
12
Ninguno de esos puntos de código es parte del sistema de escritura de ningún lenguaje natural, por lo que si alguno de ellos es aceptable como parte de un identificador es casi "accidental", según la clasificación Unicode en lugar de cualquier respaldo explícito de Python.
Chepner