2015-12-04 23 views
20

Właśnie natknąłem się na następującej sytuacji nieparzystej:Normalizacja identyfikatora: Dlaczego mikro znak jest konwertowany na grecką literę 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') 

Charakter wszedłem jest zawsze znakiem μ na klawiaturze, ale z jakiegoś powodu zostanie przekonwertowany. Dlaczego to się dzieje?

Odpowiedz

21

Występują tutaj dwa różne znaki. Jednym z nich jest MICRO SIGN, który jest tym na klawiaturze, a drugi to GREEK SMALL LETTER MU.

Aby zrozumieć, co się dzieje, powinniśmy przyjrzeć się, jak Python definiuje identyfikatory w language reference:

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

zarówno naszych bohaterów, Micro SIGN i greckim mała litera MU, są częścią grupy Ll unicode (małe litery), więc obie mogą być używane w dowolnej pozycji w identyfikatorze. Teraz należy zauważyć, że definicja identifier w rzeczywistości odnosi się do xid_start i xid_continue i są one zdefiniowane jako wszystkie znaki w odpowiedniej definicji nieposiadającej x, której normalizacja NFKC prowadzi do prawidłowej sekwencji znaków dla identyfikatora.

Python najwyraźniej troszczy się jedynie o znormalizowaną formę identyfikatorów . Potwierdzają to nieco poniżej

Identyfikatory są przekształcane do postaci normalnej NFKC podczas przetwarzania; porównanie identyfikatorów opiera się na NFKC.

NFKC to Unicode normalization, który rozkłada znaki na pojedyncze części. MICRO SIGN rozkłada się na GREEK SMALL LETTER MU i właśnie to się tam dzieje.

Istnieje wiele innych znaków, na które wpływa ta normalizacja. Inny przykład to OHM SIGN, który rozkłada się na GREEK CAPITAL LETTER OMEGA. Korzystanie że jako identyfikator daje podobny wynik, tutaj pokazane za pomocą miejscowych:

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

Więc w końcu, to jest po prostu coś, że Python robi. Niestety, nie istnieje dobry sposób na wykrycie tego zachowania, powodując błędy, takie jak ten pokazany. Zwykle, gdy identyfikator jest nazywany tylko identyfikatorem, tj. Jest używany jak prawdziwa zmienna lub atrybut, wszystko będzie dobrze: Normalizacja jest uruchamiana za każdym razem, a identyfikator zostaje znaleziony.

Jedynym problemem jest dostęp oparty na łańcuchach. Struny to tylko struny, oczywiście nie ma normalizacji (to byłby po prostu zły pomysł). A oba sposoby przedstawione tutaj, getattr i locals, działają w słownikach. getattr() uzyskuje dostęp do atrybutu obiektu za pomocą obiektu __dict__, a locals() zwraca słownik. W słownikach klucze mogą być dowolnym ciągiem znaków, więc dobrze jest mieć tam znak MICRO SIGN lub OHM SIGN.

W takich przypadkach należy pamiętać o samodzielnym przeprowadzeniu normalizacji.Możemy wykorzystać unicodedata.normalize dla tego, który następnie również pozwala nam prawidłowo dostać naszą wartość od wewnątrz locals() (lub za pomocą getattr):

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

który był bardzo jasny i dokładny. Nadal staram się unikać znaków spoza ASCII nawet w literałach łańcuchowych, nie mówiąc już o nazwach zmiennych. Normalizacja to tylko jeden problem, niektóre rzeczy mogą zostać zmanipulowane przez niektóre edytory, skopiować i wkleić zmieniające kodowanie itp. 'Class Test: mu = 'foo'' – Galax

+1

Tak długo, jak używasz UTF-8 do plików źródłowych (które naprawdę powinieneś), w większości przypadków masz się dobrze w Pythonie 3, szczególnie w literałach ciągów. Jeśli masz edytor, który może to zepsuć, powinieneś uzyskać lepszy edytor;) A jeśli chodzi o identyfikatory, możesz być kreatywny również tam, z wyjątkiem pokazanego problemu, który może powodować problemy dla niektórych lub przejść zupełnie niezauważony dla innych :) – poke