如何比较具有不同字节但相同值的Unicode字符串?

60

我正在比较JSON对象中的Unicode字符串。

它们具有相同的值:

a = '人口じんこうに膾炙かいしゃする'
b = '人口じんこうに膾炙かいしゃする'

但它们具有不同的Unicode表示:

String a : u'\u4eba\u53e3\u3058\u3093\u3053\u3046\u306b\u81be\u7099\u304b\u3044\u3057\u3083\u3059\u308b'
String b : u'\u4eba\u53e3\u3058\u3093\u3053\u3046\u306b\u81be\uf9fb\u304b\u3044\u3057\u3083\u3059\u308b'

我该如何比较两个 Unicode 字符串的值?


3
如果你能正确格式化代码,包括字符串的闭合引号等等,那将大有帮助,这样我们就可以直接复制粘贴并进行测试。请注意不要改变原本的意思,让内容更加通俗易懂,但不需要额外解释。 - abarnert
6
我猜问题在于Unicode规范化... - abarnert
2
为什么\u7099和\uf9fb是一样的? - Thorbjørn Ravn Andersen
4
好的,我会尽力进行翻译。@ThorbjørnRavnAndersen [U+F9FB](https://www.compart.com/en/unicode/U+F9FB)是一个[CJK兼容汉字](https://en.wikipedia.org/wiki/CJK_Compatibility_Ideographs),其分解为U+7099。 - phuclv
有趣的是,在JS REPL中,这些字符串是相等的。 - pushkin
3
将“bytes”翻译为“字节”,将“code points”翻译为“代码点”,原文意思是建议使用“不同的代码点”而不是“不同的字节”。 - The Impaler
3个回答

64

Unicode规范化可以帮助您解决这个问题:

>>> import unicodedata
>>> unicodedata.normalize("NFC", "\uf9fb") == "\u7099"
True

在使用==进行比较之前,使用unicodedata.normalize对两个字符串进行规范化以检查其是否具有规范的Unicode等价性。

字符U+F9FB是一个“CJK兼容”字符。这些字符规范化后分解为一个或多个常规CJK字符。


5
Ry,你为什么回退了我的修改?建议人们转换为NFC并进行比较“通常足够好”,但这并不完全正确。UCA存在有很好的理由,它不在stdlib中并不意味着没有易于获取的实现。 - abarnert
16
@abarnert表示不喜欢推荐未经过源代码审核的库,而且不清楚这里是否需要它。你可以自己撰写答案。此外,使用pyuca需要更多的指导。 (编辑:事实证明并非如此,我想到的是PyICU,现在wilx的答案已经涵盖了这一点,请阅读他的回答) - Ry-
2
应该强调的是,这并不能解决所有类似的问题,==是一个复杂的问题,需要认真思考。必须考虑在每个特定上下文中==的含义。在所有语言中都是相同的吗?它是一个标识符吗?您想忽略纯粹的呈现差异吗?这是一个攻击面吗? - Yakk - Adam Nevraumont
1
一个 NFC 认为不同但用户可能认为相同的例子是 'µ'(微符号)和 'μ'(希腊小写字母 mu)。NFKC 会将这两个字符合并。 - Eponymous

48

字符U+F9FB(炙)是一个CJK兼容汉字。这些字符与普通CJK字符有着不同的代码点,但在规范化时会分解为一个或多个普通CJK字符。

Unicode有一个官方的字符串排序算法,称为UCA,专门为此目的而设计。截至Python 3.7,Python暂不支持UCA。*但是有第三方库可以使用,例如pyuca

>>> from pyuca import Collator
>>> ck = Collator().sort_key
>>> ck(a) == ck(b)
True

对于这种情况——以及许多其他情况,但绝非全部——在比较之前选择适当的规范化应用于两个字符串将会起作用,而且它具有内置于stdlib中的支持优势。

* 这个想法原则上自3.4版本以来就被接受了,但是没有人写出实现代码——部分原因是大多数关心此事的核心开发人员正在使用pyuca或两个ICU绑定之一,它们具有在当前和旧版本的Python中工作的优势。


感谢您告诉我测试中的问题。我通过规范化Unicode字符串解决了Unicode比较测试。 - Seunghoon Baek

3

我会使用PyICU和它的Collator类。但首先,你需要考虑在哪个Unicode排序算法级别上你想要相等性发生。

#!/usr/bin/python
# -*- coding: utf-8 -*-

from icu import Collator

coll = Collator.createInstance()
coll.setStrength(Collator.IDENTICAL)

a = u'人口じんこうに膾炙かいしゃする'
b = u'人口じんこうに膾炙かいしゃする'
print repr(a)
print repr(b)
print ('%s == %s : %s' % (a, b, coll.equals(a,b)))

a = u'エレベーター'
b = u'エレベーター'
print ('%s == %s : %s' % (a, b, coll.equals(a,b)))

coll.setStrength(Collator.PRIMARY)
print ('%s == %s : %s' % (a, b, coll.equals(a,b)))

a = u'hello'
b = u'HELLO'
coll.setStrength(Collator.PRIMARY)
print ('%s == %s : %s' % (a, b, coll.equals(a,b)))

coll.setStrength(Collator.TERTIARY)
print ('%s == %s : %s' % (a, b, coll.equals(a,b)))

这将输出:
u'\u4eba\u53e3\u3058\u3093\u3053\u3046\u306b\u81be\u7099\u304b\u3044\u3057\u3083\u3059\u308b'
u'\u4eba\u53e3\u3058\u3093\u3053\u3046\u306b\u81be\uf9fb\u304b\u3044\u3057\u3083\u3059\u308b'
人口じんこうに膾炙かいしゃする == 人口じんこうに膾炙かいしゃする : True
エレベーター == エレベーター : False
エレベーター == エレベーター : True
hello == HELLO : True
hello == HELLO : False

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