从Unicode字符串中正确提取表情符号

22

我正在使用Python 2,我有一个字符串包含表情符号以及其他Unicode字符。我需要将它转换为列表,其中列表中的每个条目都是单个字符/表情符号。

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']

我如何实现所需的输出?


1
输入的字符串有7个字符,将表情符号视为单个字符。我得到的输出在列表中有11个条目。我需要获得一个输出列表,其中包含与输入字符串中的字符对应的7个条目。 - Aaron
这是哪个版本的Python?在Python 2中,x ='xyz'是非法的(或者可能会被误解)。 - ivan_pozdeev
2
在 Python 中获取包含代理对的字符串的正确长度 - ivan_pozdeev
1
@ivan_pozdeev:必须使用Python 2,因为实际输出使用u'...'字符串字面量表示值。这也凸显出这个问题缺少一个实际的[mcve]。要么是缺少from __future__ import unicode_literals,要么是缺少x字符串定义前缀的u - Martijn Pieters
@Aaron:问题标题应该更改以反映您实际的较窄问题,而被接受的答案则涵盖了这个问题。否则,从标题进来的谷歌访客可能会感到失望,因为他们期望得到更广泛问题的答案。 - jfs
显示剩余5条评论
2个回答

17
首先,在Python2中,您需要使用Unicode字符串(u'<...>')才能将Unicode字符视为Unicode字符。如果要使用字符本身而不是源代码中的\UXXXXXXXX表示,请使用正确的源编码

现在,根据Python: getting correct string length when it contains surrogate pairsPython returns length of 2 for single Unicode character string,在Python2的“窄”构建中(具有sys.maxunicode==65535),32位Unicode字符被表示为代理对,并且这对于字符串函数来说并不透明。这只在3.3中得到修复(PEP0393)。

最简单的解决方案(除了迁移到3.3+之外)是按照第三个链接中概述的方式从源代码编译Python“宽”构建。在其中,Unicode字符都是4字节(因此可能占用大量内存),但是如果您需要经常处理宽Unicode字符,则这可能是可以接受的代价。

“窄”构建的解决方案创建一组自定义字符串函数lenslice;可能作为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

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

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

2
请注意,随着最近对表情符号的各种花哨添加,这个功能有些失效了,因为一些表情符号由多个代码点组成。例如旗帜("")和人种变体("" vs ""),还有一些其他像组合变音符号 "à" 这样的东西。 - roeland
@roeland,然后需要升级is_surrogate以便能够检测这些内容,并返回额外的单词数量(=2字节字符),而不是True/False。前提是我们对这些情况感兴趣(如果你问我,控制字符和变音符号是完全不同的问题),并且其他工具如规范化无法完成此任务。 - ivan_pozdeev
2
我认为规范化无法处理那些表情符号。严格正确的答案应该迭代图形簇,长而深奥的解释在Unicode®标准附录#29中。但是如果没有能够处理它的库,我可能会坚持迭代代码点。 - roeland
@roeland:即使是\X正则表达式,在一般情况下也无法帮助,例如,某些(聊天)软件将:)(U+003a U+0029)显示为笑脸(图片),即在给定上下文中它是一个表情符号。 - jfs
@J.F.Sebastian 是的。曾经我们会打冒号和右括号。真正老派的人还会打一个横杠 :-) 。但我认为楼主问的是关于Unicode表情符号的。 - roeland
@roeland :) 在当前版本的 iPhone Skype 上运行。 它显示为笑脸(图像) - 这是“表情符号”的字面定义:“用于在电子通信中表达思想或情感的小数字图像或图标”\X 对于一般情况不够用。问题标题太广泛。 - jfs

10

我会使用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编码的字符串形式列出字符列表。


1
你的答案没有输出所需的结果。 - otorrillas
1
好的,我会添加转换以提供问题所要求的精确内容。 - James Hopkin
@James Hopkin,你能提供一种方法吗,通过这种方法我们可以将这些表情符号转换成Unicode编码,例如在Python 3中将 转换为u'\U0001f618'。 - Shubham Sharma
你可以编写以下代码:''.encode('unicode_escape')。虽然它产生的是字节而不是字符串:b'\\U0001f618' - James Hopkin

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