从 Unicode 字符串中正确提取表情符号
Correctly extract Emojis from a Unicode string
我在 Python 2 工作,我有一个包含表情符号和其他 unicode 字符的字符串。我需要将其转换为一个列表,其中列表中的每个条目都是一个 character/emoji.
x = u'xyz'
char_list = [c for c in x]
期望的输出是:
['', '', 'x', 'y', 'z', '', '']
实际输出为:
[u'\ud83d', u'\ude18', u'\ud83d', u'\ude18', u'x', u'y', u'z', u'\ud83d', u'\ude0a', u'\ud83d', u'\ude0a']
我怎样才能达到预期的输出?
首先,在Python2中,您需要使用Unicode字符串(u'<...>'
),Unicode字符才能被视为Unicode字符。 correct source encoding 如果您想使用字符本身而不是源代码中的 \UXXXXXXXX
表示。
现在,根据 Python: getting correct string length when it contains surrogate pairs and , in Python2 "narrow" builds (with sys.maxunicode==65535
), 32-bit Unicode characters are represented as surrogate pairs, and this is not transparent to string functions. This has only been fixed in 3.3 (PEP0393)。
最简单的解决方案(迁移到 3.3+ 除外)是从源代码编译 Python "wide" 构建,如第 3 日 link 所述。 其中,Unicode 字符都是 4 字节(因此可能会占用大量内存),但如果您需要经常处理宽 Unicode 字符,这可能是一个可以接受的价格。
"narrow" build 的解决方案是创建一组自定义字符串函数 (len
, slice
; 可能作为 unicode
) 的子类,它会检测代理项对并将它们作为单个字符处理。我无法轻易找到现有的(这很奇怪),但写起来并不难:
- 根据UTF-16#U+10000 to U+10FFFF - Wikipedia,
- 第一个字符(高代理项)在
0xD800..0xDBFF
范围内
- 第二个字符(低代理项) - 在范围
0xDC00..0xDFFF
- 这些范围是保留的,因此不能作为常规字符出现
下面是检测代理对的代码:
def is_surrogate(s,i):
if 0xD800 <= ord(s[i]) <= 0xDBFF:
try:
l = s[i+1]
except IndexError:
return False
if 0xDC00 <= ord(l) <= 0xDFFF:
return True
else:
raise ValueError("Illegal UTF-16 sequence: %r" % s[i:i+2])
else:
return False
还有一个 returns 简单切片的函数:
def slice(s,start,end):
l=len(s)
i=0
while i<start and i<l:
if is_surrogate(s,i):
start+=1
end+=1
i+=1
i+=1
while i<end and i<l:
if is_surrogate(s,i):
end+=1
i+=1
i+=1
return s[start:end]
在这里,您付出的代价是性能,因为这些函数比内置函数慢得多:
>>> ux=u"a"*5000+u"\U00100000"*30000+u"b"*50000
>>> timeit.timeit('slice(ux,10000,100000)','from __main__ import slice,ux',number=1000)
46.44128203392029 #msec
>>> timeit.timeit('ux[10000:100000]','from __main__ import slice,ux',number=1000000)
8.814016103744507 #usec
我会使用 uniseg 库 (pip install uniseg
):
# -*- coding: utf-8 -*-
from uniseg import graphemecluster as gc
print list(gc.grapheme_clusters(u'xyz'))
输出[u'\U0001f618', u'\U0001f618', u'x', u'y', u'z', u'\U0001f60a', u'\U0001f60a']
和
[x.encode('utf-8') for x in gc.grapheme_clusters(u'xyz'))]
将提供字符列表作为 UTF-8 编码字符串。
我在 Python 2 工作,我有一个包含表情符号和其他 unicode 字符的字符串。我需要将其转换为一个列表,其中列表中的每个条目都是一个 character/emoji.
x = u'xyz'
char_list = [c for c in x]
期望的输出是:
['', '', 'x', 'y', 'z', '', '']
实际输出为:
[u'\ud83d', u'\ude18', u'\ud83d', u'\ude18', u'x', u'y', u'z', u'\ud83d', u'\ude0a', u'\ud83d', u'\ude0a']
我怎样才能达到预期的输出?
首先,在Python2中,您需要使用Unicode字符串(u'<...>'
),Unicode字符才能被视为Unicode字符。 correct source encoding 如果您想使用字符本身而不是源代码中的 \UXXXXXXXX
表示。
现在,根据 Python: getting correct string length when it contains surrogate pairs and sys.maxunicode==65535
), 32-bit Unicode characters are represented as surrogate pairs, and this is not transparent to string functions. This has only been fixed in 3.3 (PEP0393)。
最简单的解决方案(迁移到 3.3+ 除外)是从源代码编译 Python "wide" 构建,如第 3 日 link 所述。 其中,Unicode 字符都是 4 字节(因此可能会占用大量内存),但如果您需要经常处理宽 Unicode 字符,这可能是一个可以接受的价格。
"narrow" build 的解决方案是创建一组自定义字符串函数 (len
, slice
; 可能作为 unicode
) 的子类,它会检测代理项对并将它们作为单个字符处理。我无法轻易找到现有的(这很奇怪),但写起来并不难:
- 根据UTF-16#U+10000 to U+10FFFF - Wikipedia,
- 第一个字符(高代理项)在
0xD800..0xDBFF
范围内
- 第二个字符(低代理项) - 在范围
0xDC00..0xDFFF
- 这些范围是保留的,因此不能作为常规字符出现
- 第一个字符(高代理项)在
下面是检测代理对的代码:
def is_surrogate(s,i):
if 0xD800 <= ord(s[i]) <= 0xDBFF:
try:
l = s[i+1]
except IndexError:
return False
if 0xDC00 <= ord(l) <= 0xDFFF:
return True
else:
raise ValueError("Illegal UTF-16 sequence: %r" % s[i:i+2])
else:
return False
还有一个 returns 简单切片的函数:
def slice(s,start,end):
l=len(s)
i=0
while i<start and i<l:
if is_surrogate(s,i):
start+=1
end+=1
i+=1
i+=1
while i<end and i<l:
if is_surrogate(s,i):
end+=1
i+=1
i+=1
return s[start:end]
在这里,您付出的代价是性能,因为这些函数比内置函数慢得多:
>>> ux=u"a"*5000+u"\U00100000"*30000+u"b"*50000
>>> timeit.timeit('slice(ux,10000,100000)','from __main__ import slice,ux',number=1000)
46.44128203392029 #msec
>>> timeit.timeit('ux[10000:100000]','from __main__ import slice,ux',number=1000000)
8.814016103744507 #usec
我会使用 uniseg 库 (pip install uniseg
):
# -*- coding: utf-8 -*-
from uniseg import graphemecluster as gc
print list(gc.grapheme_clusters(u'xyz'))
输出[u'\U0001f618', u'\U0001f618', u'x', u'y', u'z', u'\U0001f60a', u'\U0001f60a']
和
[x.encode('utf-8') for x in gc.grapheme_clusters(u'xyz'))]
将提供字符列表作为 UTF-8 编码字符串。