2015-12-04 5 views
20

Ich stolperte über die folgende merkwürdige Situation:Identifier Normalisierung: Warum wird das Mikrozeichen in den griechischen Buchstaben mu umgewandelt?

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

Der Charakter, den ich eingegeben ist immer das μ-Zeichen auf der Tastatur, aber aus irgendeinem Grund es umgewandelt wird. Warum passiert das?

Antwort

21

Hier sind zwei verschiedene Zeichen beteiligt. Einer ist der MICRO SIGN, der auf der Tastatur ist, und der andere ist GREEK SMALL LETTER MU.

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

Sowohl unsere Charaktere, MICRO SIGN und griechische SMALL LETTER MU, sind Teil der Ll Unicode-Gruppe:

Um zu verstehen, was vor sich geht, wir einen Blick auf, wie Python definiert Identifikatoren im language reference nehmen sollte (Kleinbuchstaben), so dass beide an jeder Position in einem Bezeichner verwendet werden können. Beachten Sie, dass die Definition von identifier sich tatsächlich auf xid_start und xid_continue bezieht, und diese sind definiert als alle Zeichen in der entsprechenden Nicht-X-Definition, deren NFKC-Normalisierung eine gültige Zeichenfolge für einen Bezeichner ergibt.

Python scheint nur interessiert an der normalisierte Form der Kennungen. Dies wird etwas unten bestätigt:

Alle Bezeichner werden beim Parsen in die normale Form NFKC konvertiert; Der Vergleich der Identifikatoren basiert auf NFKC.

NFKC ist eine Unicode normalization, die Zeichen in einzelne Teile zerlegt. Das MICRO SIGN zerfällt in GREEK SMALL LETTER MU, und genau das passiert dort.

Es gibt viele andere Zeichen, die ebenfalls von dieser Normalisierung betroffen sind. Ein anderes Beispiel ist OHM SIGN, das sich in GREEK CAPITAL LETTER OMEGA zerlegt. Unter Verwendung, der als Bezeichner ein ähnliches Ergebnis ergibt, hier Einheimischen gezeigt mit:

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

Also am Ende, das ist etwas, nur die Python tut. Leider gibt es nicht wirklich eine gute Möglichkeit, dieses Verhalten zu erkennen und Fehler wie die gezeigte zu verursachen. Normalerweise, wenn die Kennung nur als Kennung bezeichnet wird, das heißt, es wie eine echte Variable oder ein Attribut verwendet wird, dann wird alles in Ordnung sein: Die Normalisierung läuft jedes Mal, und die Kennung gefunden wird.

Das einzige Problem ist mit String-basierten Zugriff. Strings sind nur Strings, natürlich findet keine Normalisierung statt (das wäre nur eine schlechte Idee). Und die beiden hier gezeigten Wege, getattr und locals, arbeiten beide mit Wörterbüchern. getattr() greift auf das Objektattribut über das Objekt __dict__ zu, und locals() gibt ein Wörterbuch zurück. Und in Wörterbüchern können Schlüssel eine beliebige Zeichenfolge sein, also ist es vollkommen in Ordnung, ein MICRO SIGN oder ein OHM SIGN dort zu haben.

In diesen Fällen müssen Sie daran denken, eine Normalisierung selbst auszuführen.Wir können unicodedata.normalize dafür nutzen, die dann können uns auch richtig unseren Wert erhalten von innen locals() (oder mit getattr):

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

Das ist sehr klar und gründlich war. Ich versuche immer noch, Nicht-ASCII-Zeichen auch in String-Literalen zu vermeiden, geschweige denn Variablennamen. Normalisieren ist nur ein Problem, Dinge können auch durch einige Editoren verstümmelt werden, Kopieren und Einfügen Ändern der Codierung usw. 'Klasse Test: mu = 'foo'' – Galax

+1

Solange Sie UTF-8 für Ihre Quelldateien (die Sie verwenden sollten Sie), in den meisten Fällen mit Python 3, vor allem in String-Literalen. Wenn du einen Editor hast, der das durcheinander bringen kann, solltest du einen besseren Editor bekommen;) Und was die Bezeichner betrifft, kannst du auch kreativ sein, außer dem angezeigten Problem, das Probleme verursachen oder für andere völlig unbemerkt bleiben kann :) – poke

Verwandte Themen