如何在不使用空格作为分隔符的语言(如中文)上进行Python的split()?

22 投票
9 回答
21913 浏览
提问于 2025-04-16 04:36

我想把一句话拆分成一个单词列表。

对于英语和一些欧洲语言,这很简单,只需要用 split() 方法就可以了。

>>> "This is a sentence.".split()
['This', 'is', 'a', 'sentence.']

但是我还需要处理像中文这样的语言,中文不使用空格来分隔单词。

>>> u"这是一个句子".split()
[u'\u8fd9\u662f\u4e00\u4e2a\u53e5\u5b50']

显然,这种方法不适用。

我该如何把这样的句子拆分成一个单词列表呢?

更新:

到目前为止,大家的回答似乎都在说这需要自然语言处理的技术,而且中文的词边界是模糊的。我不太明白为什么会这样。对我来说,中文的词边界似乎是非常明确的。每个中文单词或字符都有对应的unicode,并且在屏幕上显示为一个独立的单词或字符。

那么,这种模糊性从哪里来呢?正如你在我的Python控制台输出中看到的,Python完全能够识别我的例句由5个字符组成:

这 - u8fd9
是 - u662f
一 - u4e00
个 - u4e2a
句 - u53e5
子 - u5b50

所以显然,Python在识别单词或字符的边界上没有问题。我只需要把这些单词或字符放到一个列表里。

9 个回答

7

好的,我搞明白了。

我需要的可以通过简单地使用 list() 来实现:

>>> list(u"这是一个句子")
[u'\u8fd9', u'\u662f', u'\u4e00', u'\u4e2a', u'\u53e5', u'\u5b50']

谢谢大家的建议。

21

你可以做到这一点,但不能使用标准库里的函数。而且正则表达式也帮不了你。

你所描述的任务属于一个叫做自然语言处理(NLP)的领域。关于如何在中文中划分单词边界,已经有很多研究成果。我建议你使用这些现有的解决方案,而不是自己去开发一个。

这种模糊性来自哪里?

你列出的那些是汉字。它们大致相当于英语中的字母或音节(但并不完全相同,正如NullUserException在评论中指出的)。汉字的边界是非常明确的,没有模糊性。但你问的不是字符边界,而是单词边界。中文单词可以由多个字符组成。

如果你只想找到字符,那这很简单,不需要使用NLP库。只需将消息解码为一个unicode字符串(如果还没有解码的话),然后用内置函数list将unicode字符串转换为一个列表。这会给你一个字符串中字符的列表。对于你的具体例子:

>>> list(u"这是一个句子")
15

这里有个小提醒:在Python 3中使用 list( '...' )(在Python 2中是 u'...')并不会给你一个unicode字符串的字符;相反,它很可能会得到一系列的16位代码点。这种情况在大多数Python安装中都是适用的,因为它们都是“窄”CPython版本。

在1990年代,unicode首次被提出时,大家认为16位足以满足全球文本编码的需求,因为它可以从128个代码点(7位)和256个代码点(8位)扩展到惊人的65,536个代码点。然而,后来很快就发现这只是美好的愿望;到现在为止,unicode 5.2版本中大约定义了100,000个代码点,还有成千上万的代码点在等待加入。为了实现这一点,unicode必须从16位(概念上)扩展到32位(虽然它并没有完全利用32位地址空间)。

为了与那些假设unicode仍然是16位的软件保持兼容,设计了所谓的代理对(surrogate pairs),即使用两个来自特定区域的16位代码点来表示超过65,536的代码点,也就是超出unicode所称的“基本多语言平面”(BMP),这些被戏称为编码的“天文”平面,因为它们相对难以捉摸,并且给文本处理和编码领域的人们带来了不少麻烦。

虽然窄CPython在某些情况下可以透明地处理代理对,但在其他情况下仍然会出现问题,比如字符串拆分就是一个比较麻烦的例子。在窄Python版本中,list( 'abc大def' )(或者用转义写作 list( 'abc\u5927\U00027C3Cdef' ))的结果会是 ['a', 'b', 'c', '大', '\ud85f', '\udc3c', 'd', 'e', 'f'],其中 '\ud85f', '\udc3c' 是一个代理对。顺便提一下,'\ud85f\udc3c' 是JSON标准要求你写的方式来表示 U-27C3C。单独的这两个代码点是没用的;一个格式正确的unicode字符串只能包含成对的代理。

所以,如果你想把字符串拆分成字符,实际上应该是:

from re import compile as _Re

_unicode_chr_splitter = _Re( '(?s)((?:[\ud800-\udbff][\udc00-\udfff])|.)' ).split

def split_unicode_chrs( text ):
  return [ chr for chr in _unicode_chr_splitter( text ) if chr ]

这样可以正确返回 ['a', 'b', 'c', '大', '', 'd', 'e', 'f'](注意:你可能可以重写正则表达式,这样就不需要过滤空字符串了)。

如果你只是想把文本拆分成中文字符,到这里基本就完成了。我不太确定提问者对“单词”的定义是什么,但对我来说,句子“这是一个句子”可以根据不同的观点拆分成 这 | 是 | 一 | 个 | 句子 或者 这是 | 一个 | 句子。不过,任何超出(可能组合的)字符和字符类别(符号、空格、字母等)的概念,都超出了unicode和Python的内置功能;你需要一些自然语言处理的技术来实现。值得一提的是,虽然你的例子 'yes the United Nations can!'.split() 确实展示了split方法对大量数据的有效性,但它并没有正确地将英语文本解析成单词:它没有识别 United Nations 是一个单词,而错误地认为 can! 是一个单词,显然这不是。这个方法会产生假阳性和假阴性。根据你的数据和你想要实现的目标,这可能不是你想要的结果。

撰写回答