Closed 5c59cbd7-8186-4351-8391-b403f3a3a73f closed 13 years ago
You cannot reliably use Unicode in Python identifiers because of the narrow/wide build issue. The enclosed file is fine on wide builds but gets compiler errors on narrow ones during compilation.
Go, Ruby, Java, and Perl all handle this situation without any problem; only Python has the bug.
Ouch! Do the rejected characters qualify as identifier characters as defined in Reference 2.3 Identifiers and keywords? http://docs.python.org/py3k/reference/lexical_analysis.html#identifiers If some interpreter version accepts extra characters, beyond the definition (as happened in 2.x), it is not a bug for for another version to only accept what is defined.
Side question: That section has "A non-normative HTML file listing all valid identifier characters for Unicode 4.1 can be found at http://www.dcl.hpi.uni-potsdam.de/home/loewis/table-3131.html." Is the set of identifier characters now larger, and if so, has the table been enlarged?
"Terry J. Reedy" \report@bugs.python.org\ wrote on Fri, 12 Aug 2011 23:05:27 -0000:
Ouch!
Do the rejected characters qualify as identifier characters as defined in Reference 2.3 Identifiers and keywords?
http://docs.python.org/py3k/reference/lexical_analysis.html#identifiers
Yes, that's right, they do. You're using the standard IDS and IDC, and XIDS and XIDC, definitions. Here were the three identifiers that were a problem:
๐๐ซ๐ฆ๐ ๐ฌ๐ก๐ข = "super"
๐๐ฏ๐
๐จ๐๐ฏ๐ป = "Deseret"
๐ฐ๐๐๐ฐโฟ๐ฟ๐ฝ๐๐ฐ๐ = "Gothic our father"
If you cannot read those, then when piped through uniquote -v
they are:
\N{MATHEMATICAL FRAKTUR CAPITAL U}\N{MATHEMATICAL FRAKTUR SMALL N}\N{MATHEMATICAL FRAKTUR SMALL I}\N{MATHEMATICAL FRAKTUR SMALL C}\N{MATHEMATICAL FRAKTUR SMALL O}\N{MATHEMATICAL FRAKTUR SMALL D}\N{MATHEMATICAL FRAKTUR SMALL E} = "super"
\N{DESERET CAPITAL LETTER DEE}\N{DESERET SMALL LETTER SHORT E}\N{DESERET SMALL LETTER ES}\N{DESERET SMALL LETTER LONG I}\N{DESERET SMALL LETTER ER}\N{DESERET SMALL LETTER SHORT E}\N{DESERET SMALL LETTER TEE} = "Deseret"
\N{GOTHIC LETTER AHSA}\N{GOTHIC LETTER TEIWS}\N{GOTHIC LETTER TEIWS}\N{GOTHIC LETTER AHSA}\N{UNDERTIE}\N{GOTHIC LETTER URUS}\N{GOTHIC LETTER NAUTHS}\N{GOTHIC LETTER SAUIL}\N{GOTHIC LETTER AHSA}\N{GOTHIC LETTER RAIDA} = "Gothic our father"
I'm not sure whether you recognize the scripts they belong to, but they're
all in the astral planes. Using uniquote -x
on them shows:
\x{1D518}\x{1D52B}\x{1D526}\x{1D520}\x{1D52C}\x{1D521}\x{1D522} = "super"
\x{10414}\x{1042F}\x{10445}\x{10428}\x{10449}\x{1042F}\x{1043B} = "Deseret"
\x{10330}\x{10344}\x{10344}\x{10330}\x{203F}\x{1033F}\x{1033D}\x{10343}\x{10330}\x{10342} = "Gothic our father"
As to whether they're proper identifiers per your reference above, I will take the first letter from each of ๐๐ซ๐ฆ๐ ๐ฌ๐ก๐ข, ๐๐ฏ๐ ๐จ๐๐ฏ๐ป, and ๐ฐ๐๐๐ฐโฟ๐ฟ๐ฝ๐๐ฐ๐, which are repsectively ๐, ๐, and ๐ฐ, or
MATHEMATICAL FRAKTUR CAPITAL U
DESERET CAPITAL LETTER DEE
GOTHIC LETTER AHSA
or
1D518
10414
10330
and show you their full Unicode properties of these reject code points. This requires the uniprops command, given which, these three commands are then completely identical:
% uniprops -ga "๐" "๐" "๐ฐ"
% uniprops -ga 1D518 10414 10330
% uniprops -ga "MATHEMATICAL FRAKTUR CAPITAL U" "DESERET CAPITAL LETTER DEE" "GOTHIC LETTER AHSA"
and produce this output:
U+1D518 โน๐โบ \N{MATHEMATICAL FRAKTUR CAPITAL U} \w \pL \p{LC} \p{L_} \p{L&} \p{Lu} All Any Alnum Alpha Alphabetic Assigned InMathematicalAlphanumericSymbols Cased Cased_Letter LC Changes_When_NFKC_Casefolded CWKCF Common Zyyy Lu L Gr_Base Grapheme_Base Graph GrBase ID_Continue IDC IDStart IDS Letter L Uppercase_Letter Math Mathematical_Alphanumeric_Symbols Print Upper Uppercase Word XID_Continue XIDC XID_Start XIDS X_POSIX_Alnum X_POSIX_Alpha X_POSIX_Graph X_POSIX_Print X_POSIX_Upper X_POSIX_Word Age=3.1 Bidi_Class=L Bidi_Class=Left_To_Right BC=L Block=Mathematical_Alphanumeric_Symbols Canonical_Combining_Class=0 Canonical_Combining_Class=Not_Reordered CCC=NR Canonical_Combining_Class=NR General_Category=Cased_Letter Script=Common Decomposition_Type=Font DT=Font Decomposition_Type=Non_Canon Decomposition_Type=Non_Canonical DT=NonCanon East_Asian_Width=Neutral GC=LC General_Category=L General_Category=Letter GeneralCategory=L General_Category=LC GC=L General_Category=Lu General_Category=Uppercase_Letter GC=Lu Grapheme_Cluster_Break=Other GCB=XX Grapheme_Cluster_Break=XX Hangul_Syllable_Type=NA Hangul_Syllable_Type=Not_Applicable HST=NA Joining_Group=No_Joining_Group JG=NoJoiningGroup Joining_Type=Non_Joining JT=U Joining_Type=U Line_Break=AL Line_Break=Alphabetic LB=AL Numeric_Type=None NT=None Numeric_Value=NaN NV=NaN Present_In=3.1 IN=3.1 Present_In=3.2 IN=3.2 Present_In=4.0 IN=4.0 Present_In=4.1 IN=4.1 Present_In=5.0 IN=5.0 Present_In=5.1 IN=5.1 Present_In=5.2 IN=5.2 Present_In=6.0 IN=6.0 SC=Zyyy Script=Zyyy Sentence_Break=UP Sentence_Break=Upper SB=UP Word_Break=ALetter WB=LE Word_Break=LE _XBegin U+10414 โน๐โบ \N{DESERET CAPITAL LETTER DEE} \w \pL \p{LC} \p{L} \p{L&} \p{Lu} All Any Alnum Alpha Alphabetic Assigned InDeseret Cased Cased_Letter LC Changes_When_Casefolded CWCF Changes_When_Casemapped CWCM Changes_When_Lowercased CWL Changes_When_NFKC_Casefolded CWKCF Deseret Dsrt Lu L Gr_Base Grapheme_Base Graph GrBase ID_Continue IDC IDStart IDS Letter L Uppercase_Letter Print Upper Uppercase Word XID_Continue XIDC XID_Start XIDS X_POSIX_Alnum X_POSIX_Alpha X_POSIX_Graph X_POSIX_Print X_POSIX_Upper X_POSIX_Word Age=3.1 Bidi_Class=L Bidi_Class=Left_To_Right BC=L Block=Deseret Canonical_Combining_Class=0 Canonical_Combining_Class=Not_Reordered CCC=NR Canonical_Combining_Class=NR General_Category=Cased_Letter Decomposition_Type=None DT=None Script=Deseret East_Asian_Width=Neutral GC=LC General_Category=L General_Category=Letter GeneralCategory=L General_Category=LC GC=L General_Category=Lu General_Category=Uppercase_Letter GC=Lu Grapheme_Cluster_Break=Other GCB=XX Grapheme_Cluster_Break=XX Hangul_Syllable_Type=NA Hangul_Syllable_Type=Not_Applicable HST=NA Joining_Group=No_Joining_Group JG=NoJoiningGroup Joining_Type=Non_Joining JT=U Joining_Type=U Line_Break=AL Line_Break=Alphabetic LB=AL Numeric_Type=None NT=None Numeric_Value=NaN NV=NaN Present_In=3.1 IN=3.1 Present_In=3.2 IN=3.2 Present_In=4.0 IN=4.0 Present_In=4.1 IN=4.1 Present_In=5.0 IN=5.0 Present_In=5.1 IN=5.1 Present_In=5.2 IN=5.2 Present_In=6.0 IN=6.0 SC=Dsrt Script=Dsrt Sentence_Break=UP Sentence_Break=Upper SB=UP Word_Break=ALetter WB=LE Word_Break=LE _XBegin U+10330 โน๐ฐโบ \N{GOTHIC LETTER AHSA} \w \pL \p{L} \p{Lo} All Any Alnum Alpha Alphabetic Assigned InGothic Gothic Is_Gothic L Lo Goth Gr_Base Grapheme_Base Graph GrBase ID_Continue IDC IDStart IDS Letter L Other_Letter Print Word XID_Continue XIDC XID_Start XIDS X_POSIX_Alnum X_POSIX_Alpha X_POSIX_Graph X_POSIX_Print X_POSIX_Word Age=3.1 Bidi_Class=L Bidi_Class=Left_To_Right BC=L Block=Gothic Canonical_Combining_Class=0 Canonical_Combining_Class=Not_Reordered CCC=NR Canonical_Combining_Class=NR Decomposition_Type=None DT=None East_Asian_Width=Neutral General_Category=L General_Category=Letter GeneralCategory=L GC=L General_Category=Lo General_Category=Other_Letter GC=Lo Script=Gothic Grapheme_Cluster_Break=Other GCB=XX Grapheme_Cluster_Break=XX Hangul_Syllable_Type=NA Hangul_Syllable_Type=Not_Applicable HST=NA Joining_Group=No_Joining_Group JG=NoJoiningGroup Joining_Type=Non_Joining JT=U Joining_Type=U Line_Break=AL Line_Break=Alphabetic LB=AL Numeric_Type=None NT=None Numeric_Value=NaN NV=NaN Present_In=3.1 IN=3.1 Present_In=3.2 IN=3.2 Present_In=4.0 IN=4.0 Present_In=4.1 IN=4.1 Present_In=5.0 IN=5.0 Present_In=5.1 IN=5.1 Present_In=5.2 IN=5.2 Present_In=6.0 IN=6.0 Script=Goth SC=Goth Sentence_Break=LE Sentence_Break=OLetter SB=LE Word_Break=ALetter WB=LE Word_Break=LE _X_Begin
As you see, all three are all of IDS, IDC, XIDS, and XIDC.
The reason they're failing in because on a narrow build, Python bizarrely splits the code points into two surrogates, and then rejects them because surrogates aren't IDanything.
This strikes me as super crazy because you are reading from a UTF-8 source, which therefore can handle all of Unicode. And you actually split the things into two UTF-16 code units, too; you don't blow up just because there is something that UCS-2 can't cope with. Indeed, I can switch things around into literals and there is no choking:
% cat astral-literals.python
#!/usr/bin/env python3.2
# -*- coding: UTF-8 -*-
super = "๐๐ซ๐ฆ๐ ๐ฌ๐ก๐ข"
Deseret = "๐๐ฏ๐
๐จ๐๐ฏ๐ป"
Gothic_our_father = "๐ฐ๐๐๐ฐโฟ๐ฟ๐ฝ๐๐ฐ๐"
print(super)
print(Deseret)
print(Gothic_our_father)
because watch what happens when I run that: mirabile visu, it behaves completely correctly despite having code points in the narrow-build- forbidden astral planes:
% python3.2 astral-literals.python
๐๐ซ๐ฆ๐ ๐ฌ๐ก๐ข
๐๐ฏ๐
๐จ๐๐ฏ๐ป
๐ฐ๐๐๐ฐโฟ๐ฟ๐ฝ๐๐ฐ๐
Again, I uniquote those for you (in case you can't read them because you haven't gotten George Douros's free Symbola font for Unicode 6.0.0 yet from http://users.teilar.gr/~g1951d/ . BTW, I also recommend his Alfios font for general text, and he has several others that may be of interest):
% python3.2 astral-literals.python | uniquote -x
\x{1D518}\x{1D52B}\x{1D526}\x{1D520}\x{1D52C}\x{1D521}\x{1D522}
\x{10414}\x{1042F}\x{10445}\x{10428}\x{10449}\x{1042F}\x{1043B}
\x{10330}\x{10344}\x{10344}\x{10330}\x{203F}\x{1033F}\x{1033D}\x{10343}\x{10330}\x{10342}
% python3.2 astral-literals.python | uniquote -v
\N{MATHEMATICAL FRAKTUR CAPITAL U}\N{MATHEMATICAL FRAKTUR SMALL N}\N{MATHEMATICAL FRAKTUR SMALL I}\N{MATHEMATICAL FRAKTUR SMALL C}\N{MATHEMATICAL FRAKTUR SMALL O}\N{MATHEMATICAL FRAKTUR SMALL D}\N{MATHEMATICAL FRAKTUR SMALL E}
\N{DESERET CAPITAL LETTER DEE}\N{DESERET SMALL LETTER SHORT E}\N{DESERET SMALL LETTER ES}\N{DESERET SMALL LETTER LONG I}\N{DESERET SMALL LETTER ER}\N{DESERET SMALL LETTER SHORT E}\N{DESERET SMALL LETTER TEE}
\N{GOTHIC LETTER AHSA}\N{GOTHIC LETTER TEIWS}\N{GOTHIC LETTER TEIWS}\N{GOTHIC LETTER AHSA}\N{UNDERTIE}\N{GOTHIC LETTER URUS}\N{GOTHIC LETTER NAUTHS}\N{GOTHIC LETTER SAUIL}\N{GOTHIC LETTER AHSA}\N{GOTHIC LETTER RAIDA}
Isn't that the weirest thing? You're acting like a perfectly fine full-unicode language even on a narrow build, and yet you reject as identifiers those same strings that you have just faithfully reproduced above.
I really do not understand this. It must be a bug. Because they are find on a wide build. And because the literals are fine even on a narrow one.
If some interpreter version accepts extra characters, beyond the definition (as happened in 2.x), it is not a bug for for another version to only accept what is defined.
Side question: That section has
"A non-normative HTML file listing all valid identifier characters for Unicode 4.1 can be found at http://www.dcl.hpi.uni-potsdam.de/home/loewis/table-3131.html ."
Gosh that's old. Unicode *4.1*, really? I sure hope you don't rely on something like that to know what's what!!
Is the set of identifier characters now larger, and if so, has the table been enlarged?
Certainly! I know this for certain science because I know that more letters have been added. But here is the proof:
% unichars -ua '\p{ID_Start}' | wc -l
100747
% unichars -ua '\p{ID_Continue}' | wc -l
102675
Notice how Unicode has been a bit oopsy with making sure that there all IDS/ISC code points also have the word property:
% unichars -ua '\w' | wc -l
102724
I'm pretty sure a few of those are slated for "fixing", because I've seen proposals to that effect. Here's one "letter" they even left out:
% unichars -gs '\w' '\pL' '\P{IDC}'
โธฏ U+02E2F GC=Lm SC=Common VERTICAL TILDE
That says to show me the code points which are
word chars
letters
*not* IDC
Before I started poking at them, nobody ran these sort of analyses so things could slip through. Even more annoyingly, here are non-word characters that are indeed IDC characters:
% unichars -gs '\W' '\p{IDC}'
ยท U+000B7 GC=Po SC=Common MIDDLE DOT
ยท U+00387 GC=Po SC=Common GREEK ANO TELEIA
แฉ U+01369 GC=No SC=Ethiopic ETHIOPIC DIGIT ONE
แช U+0136A GC=No SC=Ethiopic ETHIOPIC DIGIT TWO
แซ U+0136B GC=No SC=Ethiopic ETHIOPIC DIGIT THREE
แฌ U+0136C GC=No SC=Ethiopic ETHIOPIC DIGIT FOUR
แญ U+0136D GC=No SC=Ethiopic ETHIOPIC DIGIT FIVE
แฎ U+0136E GC=No SC=Ethiopic ETHIOPIC DIGIT SIX
แฏ U+0136F GC=No SC=Ethiopic ETHIOPIC DIGIT SEVEN
แฐ U+01370 GC=No SC=Ethiopic ETHIOPIC DIGIT EIGHT
แฑ U+01371 GC=No SC=Ethiopic ETHIOPIC DIGIT NINE
แง U+019DA GC=No SC=New_Tai_Lue NEW TAI LUE THAM DIGIT ONE
โ U+02118 GC=Sm SC=Common SCRIPT CAPITAL P
โฎ U+0212E GC=So SC=Common ESTIMATED SYMBOL
ใ U+0309B GC=Sk SC=Common KATAKANA-HIRAGANA VOICED SOUND MARK
ใ U+0309C GC=Sk SC=Common KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK
Digits are supposed to be GC=Nd not GC=No, so I don't know what the story is there. Maybe they don't have a zero?
% uniprops 'ETHIOPIC DIGIT ZERO'
uniprops: no character named โนETHIOPIC DIGIT ZEROโบ
% uniprops 'ETHIOPIC DIGIT ONE'
U+1369 โนแฉโบ \N{ETHIOPIC DIGIT ONE}
\pN \p{No}
All Any Assigned InEthiopic Ethiopic Is_Ethiopic Ethi N No Gr_Base Grapheme_Base Graph GrBase ID_Continue IDC Number
Other_Number Print XID_Continue XIDC X_POSIX_Graph X_POSIX_Print
I guess that's it then: you can't assemble bigendian base-10 integers out of digits whose set lacks a zero, which is the key criterion for GC=Nd and for NT=De (Numeric_Type=Decimal), as in regular digits:
% uniprops -ga 1
U+0031 โน1โบ \N{DIGIT ONE} \w \d \pN \p{Nd} AHex ASCII_Hex_Digit All Any Alnum ASCII Assigned Basic_Latin Common Zyyy Decimal_Number Digit Nd N Gr_Base Grapheme_Base Graph GrBase Hex XDigit Hex_Digit ID_Continue IDC Number PerlWord POSIX_Alnum POSIX_Digit POSIX_Graph POSIX_Print POSIX_Word POSIX_XDigit Print Word XID_Continue XIDC X_POSIX_Alnum X_POSIX_Digit X_POSIX_Graph X_POSIX_Print X_POSIX_Word X_POSIX_XDigit Age=1.1 Block=Basic_Latin Bidi_Class=EN Bidi_Class=European_Number BC=EN Block=ASCII BLK=ASCII Canonical_Combining_Class=0 Canonical_Combining_Class=Not_Reordered CCC=NR Canonical_Combining_Class=NR Script=Common General_Category=Decimal_Number Decomposition_Type=None DT=None East_Asian_Width=Na East_Asian_Width=Narrow EA=Na GC=Nd General_Category=Digit General_Category=Number General_Category=Nd Grapheme_Cluster_Break=Other GCB=XX Grapheme_Cluster_Break=XX Hangul_Syllable_Type=NA Hangul_Syllable_Type=Not_Applicable HST=NA Joining_Group=No_Joining_Group JG=NoJoiningGroup Joining_Type=Non_Joining JT=U Joining_Type=U Line_Break=NU Line_Break=Numeric LB=NU Numeric_Type=De Numeric_Type=Decimal NT=De Numeric_Value=1 NV=1 Present_In=1.1 IN=1.1 Present_In=2.0 IN=2.0 Present_In=2.1 IN=2.1 Present_In=3.0 IN=3.0 Present_In=3.1 IN=3.1 Present_In=3.2 IN=3.2 Present_In=4.0 IN=4.0 Present_In=4.1 IN=4.1 Present_In=5.0 IN=5.0 Present_In=5.1 IN=5.1 Present_In=5.2 IN=5.2 Present_In=6.0 IN=6.0 SC=Zyyy Script=Zyyy Sentence_Break=NU Sentence_Break=Numeric SB=NU Word_Break=NU Word_Break=Numeric WB=NU _X_Begin
But back to the mismatch between \w and IDC. Isn't that really annoying?
The first two occur in NFD, which is a pain. So you start with something that is all \w, then you decompose it, and whammo.
This brings up a whole nother issue. Insofar as they may differ, should one track \w or should one track IDS/IDC? It is extremely vexing, because whatever you do, you will change the lexical texture of your language. We recently swapped around from one to the other, and there was one person whose module had to change. He had no idea he was even using one of these things in an ident, and was happy to change it. But we lucked out that time. It is terribly bothersome situation, and I do not know what the best strategy actually is, or the safest one, nor whether those differ. It troubles us, too, I do promise you.
One thing I might do to trouble you less...
If you don't have the uniquote, uniprops, or unichars commands yet, and would like them, then well *those* I can tell you where to find. :)
--tom
New changeset 787ed1a7aba8 by Benjamin Peterson in branch '3.2': in narrow builds, make sure to test codepoints as identifier characters (closes bpo-12732) http://hg.python.org/cpython/rev/787ed1a7aba8
New changeset 5af15f018e20 by Benjamin Peterson in branch 'default': merge 3.2 (bpo-12732) http://hg.python.org/cpython/rev/5af15f018e20
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields: ```python assignee = None closed_at =
created_at =
labels = ['interpreter-core', 'type-bug']
title = "Can't portably use Unicode in Python identifiers"
updated_at =
user = 'https://bugs.python.org/tchrist'
```
bugs.python.org fields:
```python
activity =
actor = 'python-dev'
assignee = 'none'
closed = True
closed_date =
closer = 'python-dev'
components = ['Interpreter Core']
creation =
creator = 'tchrist'
dependencies = []
files = ['22882']
hgrepos = []
issue_num = 12732
keywords = []
message_count = 4.0
messages = ['141923', '141996', '142003', '142007']
nosy_count = 10.0
nosy_names = ['lemburg', 'loewis', 'terry.reedy', 'vstinner', 'ezio.melotti', 'mrabarnett', 'Arfrever', 'daniel.urban', 'python-dev', 'tchrist']
pr_nums = []
priority = 'normal'
resolution = 'fixed'
stage = 'resolved'
status = 'closed'
superseder = None
type = 'behavior'
url = 'https://bugs.python.org/issue12732'
versions = ['Python 3.2']
```