Python3 中,标识符为非ASCII Unicode字符时的出人意料的行为。

4
以下代码未发生断言错误:
K = 'K'
 = ''
 = ''
 = ''
 = ''
 = ''
ᴷ = 'ᴷ'
assert K ==  ==  ==  ==  == ᴷ
print(f'{K=}, {=}, {=}, {=}, {=}, {=}')

并打印输出

K='ᴷ', ='ᴷ', ='', ='ᴷ', ='ᴷ', ='ᴷ'

我知道 https://peps.python.org/pep-3131/ ,并阅读了有关标识符的Python文档 https://docs.python.org/3/reference/lexical_analysis.html#identifiers ,但没有找到任何提示解释经验丰富的行为。

因此,我的问题是:如果将新值分配给其中一个标识符,那么为什么所有其他视觉上不同的标识符的值不会改变?

更新:考虑到当前可用的评论和答案,需要进一步解释我对问题的满意答案的期望:

关于比较标识符名称背后的NFKC转换的提示有助于理解为什么出现了经验丰富的行为,但是...它仍然让我有一个问题开放,即为什么选择在不同的情况下使用不同的Unicode字符串比较方法?它们出现的上下文?

与字符串字面量相比,指定标识符名称的相同字符串之间的比较方式显然有所不同。

我仍然需要了解什么才能看到Unicode字符串表示Python标识符名称的方式不同于表示字符串字面量的Unicode字符串之间不进行相同比较的深层原因?

如果我理解正确,Unicode具有使用一个代码点表示复杂字符或使用适当的基本字符及其修改器表示多个代码点的模糊规范的可能性。然后,Unicode字符串的规范化是试图在首次引入这种歧义可能性时解决引起混乱的方式。但是,这是对Unicode可视化工具(如查看器和编辑器)产生影响最大的Unicode特定内容。使用将字符串表示为大于255的整数值列表(Unicode代码点)的编程语言实际上是另一回事,不是吗?

以下是进一步尝试找到更好的问题措辞:

创建两个不同的Unicode字符串最终被认为不同的可能性的优势是什么,如果它们用作Python标识符的名称?

实际功能是我认为由于破坏的WYSIWYG能力而导致不合理行为背后的功能是什么?

以下是更多的代码,说明正在发生的情况并演示源自相同字符串的字符串文字和标识符名称之间的比较差异:

from unicodedata import normalize as normal
itisasitisRepr = [                char       for char in ['K', '', '', '', '', '', 'ᴷ']]
hexintasisRepr = [         f'{ord(char):5X}' for char in itisasitisRepr]
normalizedRepr = [ normal('NFKC', char)      for char in itisasitisRepr]
hexintnormRepr = [         f'{ord(char):5X}' for char in normalizedRepr]
print(itisasitisRepr)
print(hexintasisRepr)
print(normalizedRepr)
print(hexintnormRepr)
print(f"{              'K' ==              ''  = }")
print(f"{normal('NFKC','K')==normal('NFKC','') = }")
print(ᴷ == , 'ᴷ' == '') # gives: True, False

提供:

['K', '', '', '', '', '', 'ᴷ']
['   4B', '1D542', '1D6B1', '1D50E', '1D576', '1D4DA', ' 1D37']
['K', 'K', 'Κ', 'K', 'K', 'K', 'K']
['   4B', '   4B', '  39A', '   4B', '   4B', '   4B', '   4B']
              'K' ==              ''  = False
normal('NFKC','K')==normal('NFKC','') = True

2
这个链接有帮助吗?特别是"NFKC正规化形式在解析时被应用于所有标识符;标识符的比较基于NFKC"这部分内容? - mcsoini
@mcsoini:是的,关于NFKC转换的提示有助于理解为什么会出现经验行为,但我仍然有一个问题,即在不同的上下文中比较Unicode字符串采用不同方法的背后深层原因是什么。如果将字符串作为字符串进行比较,与指定标识符名称时比较相同的字符串的方式不同。 - Claudio
1个回答

9

使用非ASCII字符的Python标识符会受到NFKC归一化(1)的影响,您可以在以下代码中看到其效果:

import unicodedata
for char in ['K', '', '', '', '', '', 'ᴷ']:
    normalised_char = unicodedata.normalize('NFKC', char)
    print(char, normalised_char, ord(normalised_char))

那的输出是:
K K 75
 K 75
 Κ 922
 K 75
 K 75
 K 75
ᴷ K 75

这表明所有但一个标识符是相同的,这就是为什么你的assert通过(它缺少了一个不同的标识符)和为什么大多数似乎是相同值的原因。实际上与以下代码没有什么不同,在这段代码中很明显会发生什么:
a = '1'
a = '2'
b = '3'
a = '4'
a = '5'
a = '6'
a = '7'
assert a == a == a == a == a == a             # passes
print(f'{a=}, {a=}, {b=}, {a=}, {a=}, {a=}')  # a=7 a=7 b=3 a=7 a=7 a=7

针对您更新的内容,具体来说是以下文本:

如果将两个不同的Unicode字符串用作Python标识符的名称时,如果它们最终被认为是不同的,则创建这种可能性有什么优势?

作为一名开发人员,我个人的观点是,我希望能够查看代码并理解它。(2)当不同的代码点映射到类似或甚至相同的字形时,这将不会容易,例如:

Ω = 1
Ω = 2
Ω = Ω + Ω
print(Ω * Ω)

您期望从这段代码中得到什么呢?您将omega设置为1,然后为2。然后您将其加倍为4,并打印平方值,即16。很简单,对吧?
实际上,在Python中,尽管该代码中存在omega和ohm字符,但它们规范化为相同的标识符,因此这正是您所获得的结果。如果它们没有被规范化,您会得到相当于:
omega = 1
ohm = 2
ohm = omega + ohm
print(ohm * ohm)

以下内容输出九而不是十六。祝你好运,调试 omegaohm 标识符之间没有区别的情况 :-)this

有些变音符号也可能有不同的表示方式,例如:

  • U+1e0b(带点的小写拉丁字母D)。
  • U+0064, U+0307(小写拉丁字母D,上方加点的组合)。

如果一个基础字母有多个变音符号,那么这可能会变得更加复杂,例如ç̇ė́。 连接标记的顺序可能是任意的,这意味着可以有许多种表示ậç̇ė́变量的方法(两个两个两个给出八个,但由于不同的代码点还存在“半重音”字符,例如ç,因此可能有更多的方法)。

不,我非常欣赏 Python 标识符发生的规范化 :-)


(1) 来自Python 标识符文档

在解析时,所有标识符都转换为 NFKC 正规形式;标识符的比较基于 NFKC。


(2) 您可以将字形视为书写的基本单位(例如字母),类似于语音单元是语音的基本单位(例如声音)。 因此,英语字形c至少有两个语音单元,即cook中的硬音和ice中的软音。

而且,更加复杂的是,cook表明一个语音单元(硬音)给出了两个不同的字形和。

现在想象一下,当您引入行星上每个其他语言时,它会变得多么复杂,我很惊讶 Unicode 联合会的成员没有完全疯掉 :-)


感谢您的关注和有益的回复。请问您能否详细解释一下NFKC规范化如何作用于单个Unicode代码点(我似乎理解了规范化的意义,即在比较时将相同字符以不同方式指定的情况进行规范化),将它们的值更改为另一个值,并且这对于什么有好处? - Claudio
一个只包含一个代码点的字符串与一个包含5000个代码点的字符串一样被规范化。长度并不重要。 - Shawn
据我所知,@Shawn,对于一个包含5000个代码点的字符串,将每个代码点进行规范化并不意味着得到一个包含5000个代码点的规范化字符串。因为规范化通常会作用于多个代码点,以便根据使用的规范化类型得出另一种顺序或另一种代码点数量。 - Claudio
@Claudio,如果你看一下https://www.compart.com/en/unicode/U+004b并点击一些基于K的字母(通常是没有额外标记或重音符号的字母),你会发现相当多的字母将它们的分解显示为ASCII K。字符串的长度在这里并不真正起作用。 - paxdiablo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接