python - significado - Normalización del identificador: ¿Por qué el signo micro se convierte en la letra griega mu?
letras griegas matlab (2)
Hay dos personajes diferentes involucrados aquí. Uno es el MICRO SIGN , que es el que está en el teclado, y el otro es GREEK SMALL LETTER MU .
Para entender lo que está pasando, deberíamos echar un vistazo a cómo Python define los identificadores en la referencia del lenguaje :
identifier ::= xid_start xid_continue*
id_start ::= <all characters in general categories Lu, Ll, Lt, Lm, Lo, Nl, the underscore, and characters with the Other_ID_Start property>
id_continue ::= <all characters in id_start, plus characters in the categories Mn, Mc, Nd, Pc and others with the Other_ID_Continue property>
xid_start ::= <all characters in id_start whose NFKC normalization is in "id_start xid_continue*">
xid_continue ::= <all characters in id_continue whose NFKC normalization is in "id_continue*">
Nuestros dos personajes, MICRO SIGN y GREEK SMALL LETTER MU, forman parte del grupo Ll
Unicode (letras minúsculas), por lo que ambos pueden usarse en cualquier posición en un identificador. Ahora note que la definición de identifier
realidad se refiere a xid_start
y xid_continue
, y esos se definen como todos los caracteres en la definición respectiva no x cuya normalización NFKC da como resultado una secuencia de caracteres válida para un identificador.
Python aparentemente solo se preocupa por la forma normalizada de identificadores. Esto se confirma un poco más abajo:
Todos los identificadores se convierten en la forma normal NFKC mientras se analiza; La comparación de identificadores se basa en NFKC.
NFKC es una normalización de Unicode que descompone los caracteres en partes individuales. El MICRO SIGN se descompone en la letra GRI GRANDE DE LA LETRA, y eso es exactamente lo que está sucediendo allí.
Hay muchos otros personajes que también se ven afectados por esta normalización. Otro ejemplo es OHM SIGN, que se descompone en letras mayúsculas griegas OMEGA . Usar eso como un identificador da un resultado similar, aquí se muestra usando locales:
>>> Ω = ''bar''
>>> locals()[''Ω'']
Traceback (most recent call last):
File "<pyshell#1>", line 1, in <module>
locals()[''Ω'']
KeyError: ''Ω''
>>> [k for k, v in locals().items() if v == ''bar''][0].encode()
b''/xce/xa9''
>>> ''Ω''.encode()
b''/xe2/x84/xa6''
Así que al final, esto es solo algo que Python hace. Desafortunadamente, no hay realmente una buena manera de detectar este comportamiento, causando errores como el que se muestra. Generalmente, cuando el identificador solo se conoce como un identificador, es decir, se usa como una variable o atributo real, entonces todo estará bien: la normalización se ejecuta cada vez, y se encuentra el identificador.
El único problema es con el acceso basado en cadenas. Las cadenas son solo cadenas, por supuesto, no hay normalización (eso sería una mala idea). Y las dos formas mostradas aquí, getattr
y locals
, operan en diccionarios. getattr()
accede al atributo de un objeto a través del __dict__
del objeto, y locals()
devuelve un diccionario. Y en los diccionarios, las claves pueden ser cualquier cadena, por lo que es perfectamente correcto tener un SIGNO MICRO o un SIGNO OHM allí.
En esos casos, debe recordar realizar una normalización usted mismo. Podemos utilizar unicodedata.normalize
para esto, lo que también nos permite obtener correctamente nuestro valor desde el interior de los locals()
(o usar getattr
):
>>> normalized_ohm = unicodedata.normalize(''NFKC'', ''Ω'')
>>> locals()[normalized_ohm]
''bar''
Me topé con la siguiente situación extraña:
>>> class Test:
µ = ''foo''
>>> Test.µ
''foo''
>>> getattr(Test, ''µ'')
Traceback (most recent call last):
File "<pyshell#4>", line 1, in <module>
getattr(Test, ''µ'')
AttributeError: type object ''Test'' has no attribute ''µ''
>>> ''µ''.encode(), dir(Test)[-1].encode()
(b''/xc2/xb5'', b''/xce/xbc'')
El carácter que ingresé es siempre el signo µ en el teclado, pero por alguna razón se convierte. ¿Por qué pasó esto?
Lo que Python hace aquí se basa en el Anexo 31 de la Norma Unicode :
Las implementaciones que toman en cuenta la normalización y el caso tienen dos opciones: tratar las variantes como equivalentes, o rechazar las variantes.
El resto de la sección proporciona más detalles, pero básicamente, esto significa que si un idioma le permite tener un identificador llamado µ
, debe tratar los dos caracteres µ
MICRO SIGN y GREEK SMALL LETTER MU de la misma manera, y debería hacerlo. así que tratándolos a ambos como GRAN CARTA PEQUEÑA MU.
La mayoría de los otros idiomas que permiten identificadores no ASCII siguen el mismo estándar; 1 sólo unos pocos idiomas inventaron el suyo propio. 2 Por lo tanto, esta regla tiene la ventaja de ser la misma en una gran variedad de idiomas (y es posible que sea compatible con IDE y otras herramientas).
Se podría argumentar que realmente no funciona tan bien en un lenguaje tan denso como el de Python, donde las cadenas pueden usarse como identificadores tan fácilmente como escribir getattr(Test, ''µ'')
. Pero si puede leer las discusiones de la lista de correo python-3000 , alrededor de PEP 3131 ; las únicas opciones que se consideraron seriamente fueron seguir con ASCII, UAX-31 o la variación menor de Java en UAX-31; Nadie quería inventar un nuevo estándar solo para Python.
La otra forma de resolver este problema sería agregar un tipo de tipo de collections.identifierdict
identificado que aplique las mismas reglas exactas para la búsqueda que el compilador aplica para los identificadores en la fuente, y usar ese tipo en las asignaciones destinadas a ser utilizadas como espacios de nombres (por ejemplo, , objeto, módulo, locales, definiciones de clase). Recuerdo vagamente que alguien sugirió eso, pero no tenía ningún buen ejemplo motivador. Si alguien piensa que este es un ejemplo lo suficientemente bueno como para revivir la idea, podría publicarla en bugs.python.org o en la lista de ideas de python .
1. Algunos lenguajes, como ECMAScript y C #, usan el "estándar Java", que se basa en una forma temprana de UAX-31 y agrega algunas extensiones menores, como ignorar los códigos de control RTL, pero eso es lo suficientemente cerca.
2. Por ejemplo, Julia permite los símbolos de moneda y matemáticas de Unicode, y también tiene reglas para mapear entre los identificadores de LaTeX y Unicode, pero agregaron explícitamente reglas para normalizar ɛ
y µ
a los últimos latinos griegos ...