Czy Python zabrania dwóch podobnie wyglądających identyfikatorów Unicode?

81

Bawiłem się identyfikatorami Unicode i natknąłem się na to:

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

Co tu się dzieje? Dlaczego Python zastępuje obiekt, do którego się odwołuje 𝑓, ale tylko czasami? Gdzie opisano to zachowanie?

Erik Cederstrand
źródło
9
To interesujące pytanie, ale twój minimalny powtarzalny przykład mógł być właśnie taki𝑓=1 f=2 print(𝑓)
khelwood
1
Dzięki. Teraz przykład stał się jeszcze mniejszy.
Erik Cederstrand
37
obxkcd
Barmar
1
a, a = 1, 2; a, a. To nie ma nic wspólnego z flub 𝑓.
user76284
4
Przykład 𝑓 = 3; fwystarczyłby.
user76284

Odpowiedzi:

81

PEP 3131 - Obsługa identyfikatorów innych niż ASCII mówi

Wszystkie identyfikatory są konwertowane do normalnej postaci NFKC podczas analizowania; Porównanie identyfikatorów oparte jest o NFKC.

Możesz użyć unicodedatado przetestowania konwersji:

import unicodedata

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

co wskazywałoby, że '𝑓'zostanie przekonwertowany na 'f'podczas analizy. Prowadząc do oczekiwanego:

𝑓  = "Some String"
print(f)
# "Some String"
Mark M.
źródło
23
To świetna odpowiedź, ale okropna decyzja twórców rdzenia Pythona. Zwracam uwagę, że w dyskusji na temat tego PEP jednym z zarzutów było to, że Unicode jest słabo rozumiany i ma słabe narzędzia. Teraz, ponad dziesięć lat później, zastanawiam się, czy nadszedł czas, aby ponownie przemyśleć latynizację identyfikatorów Unicode.
Adam Smith
33
@AdamSmith, ale normalizacja Unicode nie jest latynizacją. Możesz mieć πjako identyfikator Pythona inny niż pdobrze. Jeśli dobrze rozumiem, składanie NFK * dotyczy znaków, które zdaniem ludzi z Unicode powinny być tymi samymi znakami na początku, ale nie można ich scalić ze względu na kompatybilność wsteczną z niektórymi starszymi kodowaniami.
lenz,
19
Istnieją dwa rodzaje równoważności znaków: kanoniczne i zgodność. Równoważność kanoniczna powinna dawać dokładnie ten sam glif, co nie ma miejsca między 𝑓 a f. NFKC normalizuje zarówno kanoniczne, jak i kompatybilne odpowiedniki, co, jak zgadzam się, jest złym wyborem dla języka programowania takiego jak Python, który rozróżnia przypadki liter: oczekuje się, że identyfikatory, które renderują się inaczej, powinny być różne. Python powinien był użyć NFC, który zapewnia, że ​​𝑓 if są różnymi rzeczami.
lvella
27
Potrzebna jest jakaś forma normalizacji, np. Ze względu na znaki łacińskie ze znakami diakrytycznymi - jeśli widzę znak typu „ü”, to może to być znak złożony (u + łączący diaerezę) lub wstępnie złożony pojedynczy znak; użytkownik nie miałby rozsądnego sposobu ani chęci ich rozróżnienia, a preferowana przez niego metoda wprowadzania danych prawdopodobnie pozwoliłaby wprowadzić tylko jedną z tych opcji. Dlatego pożądane jest, aby jeśli widzę „ü” i napiszę „ü”, język uważa znaki za równoważne, nawet jeśli są one inaczej zakodowane, chociaż normalizacja NFC prawdopodobnie byłaby do tego wystarczająca.
Peteris
8
Python obsługuje Unicode dla identyfikatorów, aby ułatwić jego użycie w definiowaniu identyfikatorów w językach innych niż angielski, a nie w celu zapewnienia równego dostępu do wszystkich punktów kodowych Unicode. Na przykład obecnie dość trudno jest zhakować parser w celu obsługi operatorów Unicode, ponieważ zakłada się, że każdy znak spoza ASCII jest częścią identyfikatora, nawet jeśli dany znak Unicode nie jest prawidłową częścią identyfikatora. Pomysł nie polega na wspieraniu wyszukiwania Unicode w poszukiwaniu „interesujących” znaków, ale na obsłudze znaków tworzonych przez standardowe inne niż angielskie układy klawiatury.
chepner
28

Oto mały przykład, aby pokazać, jak okropna jest ta „funkcja”:

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

Wypróbuj online! (Ale proszę, nie używaj tego)

Jak wspomniał @MarkMeyer, dwa identyfikatory mogą się różnić, nawet jeśli wyglądają tak samo („CYRYLOWA WIELKA LITERA A” i „ŁACIŃSKA WIELKA LITERA A”)

А = 42
print(A)
# => NameError: name 'A' is not defined
Eric Duminil
źródło
3
Sprawia, że ​​chcę napisać odpowiednik jsfuck.com ... python-unicode-hell.com?
Mathieu VIALES
2
@MathieuVIALES 𝓕𝕖𝒆𝑙 𝐟ʳ𝙚ₑ ᵗ𝗈 ᵈ𝚘 𝓈º. I 𝐡a𝔳ᵉ 𝒔𝚘𝙢𝖾 𝒄𝑜𝖽ᵉ 𝖑𝒶𝒚𝑖𝒏𝕘 arₒ𝘶𝘯𝖽. 𝐈 ʷ𝙖n𝓉ℯ𝙙 𝒕𝘰 𝗍𝕣o𝑙𝗅 ⅽ𝔬𝚕𝘭ᵉ𝗮𝓰𝘶𝖊𝔰 ʷ𝚒ₜ𝙝 𝓲ᵗ, 𝕓𝒖t 𝚝ℎₑ 𝗋𝑒𝙨𝓊𝕝𝓉 ⅈ𝔰 𝓳ᵘ𝑠𝙩 t𝚘𝗈 𝗵o𝒓𝑟ible 𝘀𝐨 𝐼 ⁿ𝚎v𝖾𝔯 ᵘ𝓼ₑⅾ ⅈt. 𝕌𝓃𝗍𝚒l 𝕟𝚘𝙬.
Eric Duminil
8
I wtedy oczywiście: А = 42; print(A)-> "NameError: name 'A' is not specified"
Mark M
8
Chodziło o to, aby nigdy nie otwierać drzwi do dowolnie złożonych nazw identyfikatorów, ale o ułatwienie wpisywania identyfikatorów w ojczystym języku programisty (przy użyciu układu klawiatury rodzimego dla tego języka). Lepiej jest posługiwać się klasyfikacją Unicode punktu kodowego jako litery niż działać jako arbiter, dla którego systemy pisma mogą i nie mogą być używane do identyfikatorów. (A ograniczenie identyfikatora do znaków z jednego systemu pisma wykracza daleko poza klasyfikację
płacową
12
Żaden z tych punktów kodowych nie jest częścią żadnego systemu pisania języka naturalnego, więc to, czy którykolwiek z nich jest akceptowalny jako część identyfikatora, jest prawie „przypadkowe”, w oparciu o klasyfikację Unicode, a nie jakiekolwiek wyraźne poparcie ze strony samego Pythona.
chepner