关于python:从Unicode字符串中正确提取Emojis

Correctly extract Emojis from a Unicode string

我在python 2中工作,我有一个包含emoji和其他unicode字符的字符串。我需要将其转换为一个列表,其中列表中的每个条目都是一个字符/emoji。

1
2
x = u'????xyz????'
char_list = [c for c in x]

所需输出为:

1
['??', '??', 'x', 'y', 'z', '??', '??']

实际输出为:

1
[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字符。如果要使用字符本身而不是源代码中的\UXXXXXXXX表示形式,请更正源代码编码。

现在,根据python:当包含代理项对时获得正确的字符串长度,并且python返回单个unicode字符串的长度2,在python2"窄"构建(使用sys.maxunicode==65535时),32位unicode字符表示为代理项对,这对字符串函数是不透明的。这只在3.3(PEP0393)中被修复。

最简单的解决方案(除了迁移到3.3+之外)是从源代码编译一个python"wide"构建,如第3个链接所述。在它中,Unicode字符都是4字节(因此可能会占用内存),但是如果您需要常规处理宽的Unicode字符,这可能是一个可以接受的价格。

"窄"构建的解决方案是定制一组字符串函数(lenslice;可能是unicode的一个子类),用于检测代理对并将其作为单个字符处理。我找不到现有的(这很奇怪),但写起来并不难:

  • 根据utf-16 u+10000至u+10ffff-维基百科,
    • 第一个字符(高代理)在0xD800..0xDBFF范围内。
    • 第二个字符(低代理)-在范围0xDC00..0xDFFF
    • 这些范围是保留的,因此不能作为常规字符出现

下面是检测代理项对的代码:

1
2
3
4
5
6
7
8
9
10
11
12
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

以及返回一个简单切片的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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]

这里,您支付的价格是性能,因为这些功能比内置的慢得多:

1
2
3
4
5
>>> 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

1
2
3
4
# -*- 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']

1
[x.encode('utf-8') for x in gc.grapheme_clusters(u'????xyz????'))]

将以utf-8编码字符串的形式提供字符列表。