Подтвердить что ты не робот

Идентификация нормализации: почему микро-знак преобразован в греческую букву mu?

Я просто наткнулся на следующую странную ситуацию:

>>> 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')

Знак, который я ввел, всегда является знаком μ на клавиатуре, но по какой-то причине он преобразуется. Почему это происходит?

4b9b3361

Ответ 1

Здесь есть два разных персонажа. Одним из них является MICRO SIGN, который находится на клавиатуре, а второй GREEK SMALL LETTER MU.

Чтобы понять, что происходит, мы должны посмотреть, как Python определяет идентификаторы в справочной системе :

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*">

Оба наших символа, MICRO SIGN и GREEK SMALL LETTER MU являются частью группы unicode Ll (строчные буквы), поэтому оба они могут использоваться в любой позиции в идентификаторе. Теперь обратите внимание, что определение identifier на самом деле относится к xid_start и xid_continue, и они определяются как все символы в соответствующем не-х определении, чья нормализация NFKC приводит к допустимой последовательности символов для идентификатора.

Python, по-видимому, заботится только о нормализованной форме идентификаторов. Это подтверждается немного ниже:

Все идентификаторы преобразуются в нормальную форму NFKC во время разбора; сравнение идентификаторов основано на NFKC.

NFKC является нормализацией Unicode, которая разлагает символы на отдельные части. MICRO SIGN распадается на GREEK SMALL LETTER MU, и именно это происходит там.

Есть много других символов, на которые также влияет эта нормализация. Еще один пример OHM SIGN, который распадается на GREEK CAPITAL LETTER OMEGA. Использование этого как идентификатора дает аналогичный результат, здесь показано с использованием locals:

>>> Ω = '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'

Итак, в конце концов, это просто то, что делает Python. К сожалению, на самом деле нет хорошего способа обнаружить это поведение, вызывая ошибки, такие как показанные. Обычно, когда идентификатор упоминается только как идентификатор, т.е. Используется как реальная переменная или атрибут, тогда все будет хорошо: нормализация выполняется каждый раз, и идентификатор найден.

Единственная проблема связана со строковым доступом. Строки - это просто строки, конечно, нормализации не происходит (это будет просто плохая идея). И два способа, показанные здесь, getattr и locals, работают на словарях. getattr() обращается к атрибуту объектов через объекты __dict__, а locals() возвращает словарь. А в словарях клавишами может быть любая строка, поэтому вполне нормально иметь MICRO SIGN или OHM SIGN.

В таких случаях вам нужно помнить о том, чтобы выполнить нормализацию самостоятельно. Мы можем использовать unicodedata.normalize для этого, что также позволяет нам правильно получить наше значение изнутри locals() (или используя getattr):

>>> normalized_ohm = unicodedata.normalize('NFKC', 'Ω')
>>> locals()[normalized_ohm]
'bar'