Verbietet Python zwei ähnlich aussehende Unicode-Bezeichner?

80

Ich habe mit Unicode-Kennungen herumgespielt und bin darauf gestoßen:

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

Was ist hier los? Warum ersetzt Python das Objekt, auf das verwiesen wird 𝑓, aber nur manchmal? Wo ist dieses Verhalten beschrieben?

Erik Cederstrand
quelle
9
Dies ist eine interessante Frage, aber Ihr minimal reproduzierbares Beispiel könnte gerade gewesen sein𝑓=1 f=2 print(𝑓)
khelwood
1
Vielen Dank. Das Beispiel wurde jetzt noch kleiner.
Erik Cederstrand
36
obxkcd
Barmar
1
a, a = 1, 2; a, a. Das hat nichts mit foder zu tun 𝑓.
user76284
4
Das Beispiel 𝑓 = 3; fwürde ausreichen.
user76284

Antworten:

79

PEP 3131 - Unterstützung von Nicht-ASCII-Kennungen sagt

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

Sie können unicodedatadie Conversions testen:

import unicodedata

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

was darauf hinweisen würde, dass beim Parsen '𝑓'konvertiert wird 'f'. Führen zu den erwarteten:

𝑓  = "Some String"
print(f)
# "Some String"
Mark Meyer
quelle
22
Dies ist eine großartige Antwort, aber eine schreckliche Entscheidung der Python-Kernentwickler. Ich stelle fest, dass in der Diskussion dieses PEP eine der Einwände darin bestand, dass Unicode schlecht verstanden wird und über schwache Werkzeuge verfügt. Jetzt, über ein Jahrzehnt später, frage ich mich, ob es an der Zeit ist, die Romanisierung von Unicode-Kennungen zu überdenken.
Adam Smith
33
@AdamSmith, aber Unicode-Normalisierung ist keine Romanisierung. Sie können πals Python-Kennung eine haben, die sich von pgut unterscheidet. Wenn ich das richtig verstehe, handelt es sich bei der NFK * -Faltung um Zeichen, von denen die Unicode-Leute anfangs dachten, sie hätten das gleiche Zeichen sein sollen, aber sie können aufgrund der Abwärtskompatibilität mit einigen älteren Codierungen nicht zusammengeführt werden.
Lenz
19
Es gibt zwei Arten der Zeichenäquivalenz: kanonisch und Kompatibilität. Die kanonische Äquivalenz sollte genau dieselbe Glyphe wiedergeben, was zwischen 𝑓 und f nicht der Fall ist. NFKC normalisiert sowohl kanonische als auch Kompatibilitätsäquivalenzen, was meiner Meinung nach eine schlechte Wahl für eine Programmiersprache wie Python ist, die zwischen Groß- und Kleinschreibung unterscheidet: Es wird erwartet, dass Bezeichner, die unterschiedlich gerendert werden, unterschiedlich sein sollten. Python sollte NFC verwendet haben, was sicherstellt, dass 𝑓 und f verschiedene Dinge sind.
lvella
27
Eine Form der Normalisierung ist beispielsweise aufgrund von lateinischen Zeichen mit diakritischen Zeichen erforderlich. Wenn ich ein Zeichen wie 'ü' sehe, kann es sich entweder um ein zusammengesetztes Zeichen (u + kombinierte Diaerese) oder um ein vorkomponiertes einzelnes Zeichen handeln. Der Benutzer hätte keine vernünftige Möglichkeit oder keinen vernünftigen Wunsch, sie zu unterscheiden, und seine bevorzugte Eingabemethode würde wahrscheinlich nur die Eingabe einer dieser Optionen ermöglichen. Wenn ich also 'ü' sehe und 'ü' eingebe, dann betrachtet die Sprache die Zeichen als äquivalent, auch wenn sie unterschiedlich codiert sind, obwohl die NFC-Normalisierung wahrscheinlich dafür ausreichen würde.
Peteris
8
Python unterstützt Unicode für Bezeichner, um die Verwendung beim Definieren von Bezeichnern in nicht englischen Sprachen zu erleichtern und nicht den gleichen Zugriff auf alle Unicode-Codepunkte zu ermöglichen. Beispielsweise ist es derzeit ziemlich schwierig, den Parser zu hacken, um Unicode-Operatoren zu unterstützen, da zunächst angenommen wird, dass jedes Nicht-ASCII-Zeichen Teil eines Bezeichners ist, selbst wenn das betreffende Unicode-Zeichen kein gültiger Teil eines Bezeichners ist. Die Idee ist nicht, das Mining von Unicode für "interessante" Zeichen zu unterstützen, sondern Zeichen zu unterstützen, die von nicht englischen Standard-Tastaturlayouts erzeugt werden.
Chepper
26

Hier ist ein kleines Beispiel, um zu zeigen, wie schrecklich diese "Funktion" ist:

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

Probieren Sie es online aus! (Aber bitte nicht benutzen)

Und wie von @MarkMeyer erwähnt, können zwei Bezeichner unterschiedlich sein, obwohl sie gleich aussehen ("CYRILLIC CAPITAL LETTER A" und "LATIN CAPITAL LETTER A").

А = 42
print(A)
# => NameError: name 'A' is not defined
Eric Duminil
quelle
3
Will ich ein Äquivalent zu jsfuck.com schreiben ... python-unicode-hell.com?
Mathieu VIALES
2
@ MathieuVIALES 𝓕𝕖𝒆𝑙 𝐟ʳ𝙚ₑ ᵗ𝗈 ᵈ𝚘 𝓈º. Ich 𝐡a𝔳ᵉ 𝒔𝚘𝙢𝖾 𝒄𝑜𝖽ᵉ 𝖑𝒶𝒚𝑖𝒏𝕘 arₒ𝘶𝘯𝖽. 𝐈 ʷ𝙖n𝓉ℯ𝙙 𝒕𝘰 𝗍𝕣o𝑙𝗅 ⅽ𝔬𝚕𝘭ᵉ𝗮𝓰𝘶𝖊𝔰 ʷ𝚒ₜ𝙝 𝓲ᵗ, 𝕓𝒖t 𝚝ℎₑ 𝗋𝑒𝙨𝓊𝕝𝓉 ⅈ𝔰 𝓳ᵘ𝑠𝙩 t𝚘𝗈 e e eⅈ 𝕌𝓃𝗍𝚒l 𝕟𝚘𝙬.
Eric Duminil
8
Und dann natürlich: А = 42; print(A)-> "NameError: Name 'A' ist nicht definiert"
Mark Meyer
8
Es ging nie darum, die Tür zu willkürlich komplexen Bezeichnernamen zu öffnen, sondern die Eingabe von Bezeichnern in der Muttersprache eines Programmierers zu erleichtern (unter Verwendung eines in dieser Sprache einheimischen Tastaturlayouts). Es ist besser, Unicodes Klassifizierung eines Codepunkts als Buchstaben zu verwenden, als als Schiedsrichter zu fungieren, für den Schreibsysteme für Bezeichner verwendet werden können und nicht. (Und die Beschränkung einer Kennung auf Zeichen aus einem einzelnen Schriftsystem liegt weit über der Gehaltsstufe des Parsers.)
chepner
11
Keiner dieser Codepunkte ist Teil des Schriftsystems einer natürlichen Sprache. Ob einer von ihnen als Teil eines Bezeichners akzeptabel ist, ist also fast "zufällig", basierend auf der Unicode-Klassifizierung und nicht auf einer expliziten Bestätigung durch Python selbst.
Chepper