Python: getting correct string length when it contains surrogate pairs
考虑在IPython上进行以下交换:
1 2 3 4
| In [1]: s = u'華袞與緼??同歸'
In [2]: len(s)
Out[2]: 8 |
正确的输出应该是7,但是由于这七个汉字中的第五个有很高的Unicode码位,所以它用"代理项对"而不是一个简单的码位来表示,因此python认为它是两个字符而不是一个。
即使我使用unicodedata,它将代理项对正确地返回为单个码点(\U00026177),当传递给len()时,仍然返回错误的长度:
1 2 3 4 5 6 7 8
| In [3]: import unicodedata
In [4]: unicodedata.normalize('NFC', s)
Out[4]: u'\u83ef\u889e\u8207\u7dfc\U00026177\u540c\u6b78'
In [5]: len(unicodedata.normalize('NFC', s))
Out[5]: 8 |
如果不采取像为utf-32重新编译python这样的极端步骤,有没有一种简单的方法可以在这种情况下获得正确的长度?
我在i python 0.13,python 2.7.2,mac os 10.8.2上。
- 这里和这里的讨论似乎是相关的。
- @DSM:谢谢你把这些挖出来。您的第一个链接显示了为UTF-32编译的Python("宽构建"),我在我的问题中排除了这一点。在第二部分中,wberry的回复显示了一段精心设计的代码,用于实际计算真实字符。我的默认解决方案与后者类似,但我希望有内置的更直接的解决方案。
- 我不能在这里重现您的结果(ubuntu-box,python 2.7.2)。对于unicode u'u83efu889eu8207u7dfcu00026177u540cu6b78'我得到长度为7的len(s)和len(unicode.normalize('nfc',s))。
- 它可能高度依赖于版本。python3.3应该更优雅地处理这个问题,因为默认情况下,它从不创建代理对(即使您可以手工创建它们)。
- 用代理项对表示非BMP字符的不是UTF-8。它是utf-16,或者更确切地说是python在版本<3.3的狭窄版本中使用的黑客。(好吧,您可以采用UTF-16中的代理对,并使用UTF-8对这两个代理对中的每一个进行编码,但这被RFC3629明确禁止,尽管许多UTF-8实现都这样做:它被称为WTF-8。但是,用这种方式对字符串进行UTF-8编码的唯一方法是,它最初来自于UTF-16)。请参阅下面的Chrispy答案,了解一个简单的解决方案。
- 顺便说一句,即使是窄构建的python 2也能用utf-8做正确的事情:对于上面提到的s,len(s.encode('utf-8'))给出22个字符,这来自于使用3个字节对7个字符中的6个进行编码,而另一个使用4个字节。这里的utf-8并没有做错误的事情,分别编码代理对(感谢上帝),这将导致8*3=24字节的长度。
我认为这已经在3.3中得到了解决。见:
http://docs.python.org/py3k/whatsnew/3.3.htmlhttp://www.python.org/dev/peps/pep-0393/(搜索wstr_length)
- 对。但是在2.7版本中,我们显然是独立的,除非我们使用的是宽广的结构。不幸的是,我还需要一段时间才能搬到PY3。
- 我在2月份搬到了Py3,并且(除了当我被诸如nltk之类的库强迫回到2.7时),我对代理对的麻烦已经结束了。这确实是目前最好的解决方案。
我在python 2上做了一个函数:
1 2 3
| SURROGATE_PAIR = re.compile(u'[\ud800-\udbff][\udc00-\udfff]', re.UNICODE)
def unicodeLen(s):
return len(SURROGATE_PAIR.sub('.', s)) |
通过将代理对替换为单个字符,我们"修复"了len函数。在普通字符串上,这应该是相当有效的:因为模式不匹配,所以返回原始字符串时不做任何修改。它也应该适用于宽(32位)的Python构建,因为不会使用代理项对编码。
- 这不适用于4字节的Unicode字符,例如?????
- @沃吉克斯特凡,应该这样做,你为什么这么说?代理对机制对任何不符合UTF-16的内容进行编码;?????例如,是D83D DCAA。
- 我希望一个二头肌字符(像上面的一样)返回1的长度,但是unicodeLen(u'\U0001f4aa\U0001f3ff')返回2。我的期望不正确吗@chrispy?
- 它不处理emoji修饰符!
您可以重写python中的len函数(请参见:len是如何工作的?)并在其中添加一个if语句以检查超长的unicode。