移除Python unicode字符串中的重音符号的最佳方法是什么?


503

我在Python中有一个Unicode字符串,我想删除所有的重音符号(变音符号)。

我在网上发现了一种用Java实现此目的的优雅方法:

  1. 将Unicode字符串转换为长规范化格式(带有单独的字母和变音符号)
  2. 删除Unicode类型为“变音符号”的所有字符。

我是否需要安装pyICU之类的库,还是仅使用python标准库就可以?那python 3呢?

重要说明:我想避免使用带有重音符号到非重音符号的显式映射的代码。

Answers:


446

Unidecode是正确的答案。它将所有unicode字符串音译为ASCII文本中最接近的可能表示形式。

例:

accented_string = u'Málaga'
# accented_string is of type 'unicode'
import unidecode
unaccented_string = unidecode.unidecode(accented_string)
# unaccented_string contains 'Malaga'and is of type 'str'

67
似乎可以很好地与中文配合使用,但不幸的是,将法语名称“François”转换为“ FranASSois”,与更自然的“ Francois”相比,效果不是很好。
埃里克·O·勒比戈

10
取决于您要实现的目标。例如,我现在正在搜索,并且我不想音译希腊语/俄语/中文,我只想用“ a / e / s / c”代替“±/ę/ś/ć”
kolinko 2012年

58
如果将unicode对象传递给@EOL unidecode,则它非常适用于“François”之类的字符串。似乎您尝试使用纯字节字符串。
Karl Bartel 2012年

26
请注意,unidecode> = 0.04.10(2012年12月)是GPL。如果需要更宽松的许可,并且使用起来会稍差一些,请使用早期版本或查看github.com/kmike/text-unidecode
米哈伊尔·科罗波夫

10
unidecode替换°deg。它所做的不仅仅是删除重音符号。
埃里克·杜米尼尔

273

这个怎么样:

import unicodedata
def strip_accents(s):
   return ''.join(c for c in unicodedata.normalize('NFD', s)
                  if unicodedata.category(c) != 'Mn')

这也适用于希腊字母:

>>> strip_accents(u"A \u00c0 \u0394 \u038E")
u'A A \u0394 \u03a5'
>>> 

字符类别 “锰”表示Nonspacing_Mark,这是类似于MiniQuark的答案unicodedata.combining(我没想到unicodedata.combining的,但它可能是更好的解决方案,因为它更明确)。

请记住,这些操作可能会大大改变文本的含义。口音,Umlauts等不是“装饰”。


6
不幸的是,这些字符不是合成字符,即使“ł”被命名为“带笔划的拉丁文小写字母L”!您要么需要使用解析功能玩游戏,要么需要unicodedata.name分解并使用相似的表格-无论如何,您都需要希腊字母(A只是“希腊大写字母ALPHA”)。
Alexis 2012年

2
@andi,恐怕我猜不出你想说什么。电子邮件交换反映了我上面写的内容:由于字母“ł”不是带重音的字母(在Unicode标准中不被视为字母),因此它没有分解。
亚历克西斯

2
@alexis(后期跟进):这对于希腊语也非常适用-例如。正如预期的那样,“带有大亚和VARIA的希腊字母大写字母”被标准化为“希腊字母大写字母”。除非您是指音译(例如“α”→“ a”),否则与“去除重音符号”不同...
lenz,2016年

@lenz,我不是在谈论从希腊语中删除重音符号,而是在讲“中风”。由于它不是变音符号,因此将其更改为Plain ell等同于将Greek Alpha更改为A。如果不希望,请不要这样做,但是在两种情况下,您都将替换拉丁字母(近似)。
亚历克西斯

通常效果很好:)但在示例中它不会转换ß为ascii ss。我仍然会使用unidecode以避免事故。
艺术

145

我刚刚在网上找到了这个答案:

import unicodedata

def remove_accents(input_str):
    nfkd_form = unicodedata.normalize('NFKD', input_str)
    only_ascii = nfkd_form.encode('ASCII', 'ignore')
    return only_ascii

它可以正常工作(例如,对于法语),但是我认为第二步(删除重音符号)比丢弃非ASCII字符要好,因为这对于某些语言(例如希腊文)会失败。最好的解决方案可能是显式删除标记为变音符号的unicode字符。

编辑:这可以解决问题:

import unicodedata

def remove_accents(input_str):
    nfkd_form = unicodedata.normalize('NFKD', input_str)
    return u"".join([c for c in nfkd_form if not unicodedata.combining(c)])

unicodedata.combining(c)如果该字符c可以与前面的字符组合,则返回true ,这主要是如果它是一个变音符。

编辑2remove_accents需要一个unicode字符串,而不是字节字符串。如果您有字节字符串,则必须将其解码为一个unicode字符串,如下所示:

encoding = "utf-8" # or iso-8859-15, or cp1252, or whatever encoding you use
byte_string = b"café"  # or simply "café" before python 3.
unicode_string = byte_string.decode(encoding)

5
我必须在unicode中添加“ utf8”:nkfd_form = unicodedata.normalize('NFKD', unicode(input_str, 'utf8'))
Jabba 2012年

@Jabba:, 'utf8'如果要在终端中测试输入(默认情况下不使用unicode),则是一个“安全网”。但通常你不会加它,因为如果你删除的口音那么input_str很可能是UTF8了。不过,安全不会有什么坏处。
MestreLion'4

1
@rbp:您应该将unicode字符串传递给remove_accents而不是常规字符串(u“é”而不是“é”)。您将常规字符串传递给remove_accents,因此在尝试将字符串转换为unicode字符串时,使用了默认ascii编码。此编码不支持任何值大于127的字节。在外壳程序中键入“é”时,您的操作系统可能使用UTF-8或某些Windows代码页编码对其进行了编码,并且包含的​​字节数> 127。我将更改函数以删除对Unicode的转换:如果传递了非Unicode字符串,它将更加清晰地轰炸。
MiniQuark 2013年

1
@MiniQuark效果很好>>> remove_accents(unicode('é'))
rbp 2013年

1
这个答案在大型数据集上给了我最好的结果,唯一的例外是“ð”-unicodedata不会碰它!
s29

43

实际上,我正在开发与项目兼容的python 2.6、2.7和3.4,并且必须从免费用户条目中创建ID。

多亏了您,我创建了一个可以实现奇迹的功能。

import re
import unicodedata

def strip_accents(text):
    """
    Strip accents from input String.

    :param text: The input string.
    :type text: String.

    :returns: The processed String.
    :rtype: String.
    """
    try:
        text = unicode(text, 'utf-8')
    except (TypeError, NameError): # unicode is a default on python 3 
        pass
    text = unicodedata.normalize('NFD', text)
    text = text.encode('ascii', 'ignore')
    text = text.decode("utf-8")
    return str(text)

def text_to_id(text):
    """
    Convert input text to id.

    :param text: The input string.
    :type text: String.

    :returns: The processed String.
    :rtype: String.
    """
    text = strip_accents(text.lower())
    text = re.sub('[ ]+', '_', text)
    text = re.sub('[^0-9a-zA-Z_-]', '', text)
    return text

结果:

text_to_id("Montréal, über, 12.89, Mère, Françoise, noël, 889")
>>> 'montreal_uber_1289_mere_francoise_noel_889'

2
使用Py2.7时,在已经传递了unicode字符串错误text = unicode(text, 'utf-8')。为此添加了一种解决方法except TypeError: pass
Daniel Reis

很吵!就我而言。Umaseleçãode poesia brasileira para desenvolver是escuta dos alunos idiomaPortuguês的一次倾覆。
亚伦

23

这不仅处理重音,而且还处理“笔画”(如ø等):

import unicodedata as ud

def rmdiacritics(char):
    '''
    Return the base character of char, by "removing" any
    diacritics like accents or curls and strokes and the like.
    '''
    desc = ud.name(char)
    cutoff = desc.find(' WITH ')
    if cutoff != -1:
        desc = desc[:cutoff]
        try:
            char = ud.lookup(desc)
        except KeyError:
            pass  # removing "WITH ..." produced an invalid name
    return char

这是我能想到的最优雅的方式(alexis在此页的评论中已经提到),尽管我认为这确实不是很优雅。实际上,正如注释中所指出的那样,这更像是一种黑客,因为Unicode名称是–实际上只是名称,它们不能保证其一致性或任何形式。

由于它们的Unicode名称中不包含“ WITH”,因此仍有一些特殊的字母无法对此进行处理,例如转弯和倒转字母。无论如何,这取决于您想做什么。有时我需要重音符号来实现字典的排序顺序。

编辑说明:

合并了注释中的建议(处理查找错误,Python-3代码)。


8
如果新符号不存在,则应捕获异常。例如,有SQUARE WITH VERTICAL FILL▥,但没有SQUARE。(更不用说此代码将带有雨滴的UMBRELLA转换为UMBRELLA☂)。
janek37 2015年

利用可用字符的语义描述看起来很优雅。我们是否真的需要unicode在其中使用python 3 进行函数调用?我认为使用更严格的正则表达式find可以避免上面评论中提到的所有麻烦,而且,当它是关键代码路径时,备忘录可以帮助提高性能。
matanster

1
@matanster不,这是Python-2时代的旧答案;该unicode类型转换是在Python 3不再适合在任何情况下,在我的经验有没有普遍的,优雅的解决了这个问题。根据应用程序的不同,任何方法都有其优缺点。诸如质量提升工具等unidecode都是基于手工制作的表格。某些资源(表,算法)由Unicode提供,例如。进行排序。
lenz

1
我只是重复一遍,上面的内容(py3):1)unicode(char)-> char 2)尝试:返回ud.lookup(desc),但KeyError除外:return char
mirek

@mirek你是对的:由于该线程非常流行,因此该答案值得一些更新/改进。我编辑了
lenz

15

回应@MiniQuark的回答:

我试图读取一个半法语的csv文件(包含重音符号)以及一些最终会变成整数和浮点数的字符串。作为测试,我创建了一个如下所示的test.txt文件:

蒙特利尔,于伯,12.89,梅尔,弗朗索瓦,诺尔,889

我必须包括行23使其起作用(在python票证中找到),并包含@Jabba的注释:

import sys 
reload(sys) 
sys.setdefaultencoding("utf-8")
import csv
import unicodedata

def remove_accents(input_str):
    nkfd_form = unicodedata.normalize('NFKD', unicode(input_str))
    return u"".join([c for c in nkfd_form if not unicodedata.combining(c)])

with open('test.txt') as f:
    read = csv.reader(f)
    for row in read:
        for element in row:
            print remove_accents(element)

结果:

Montreal
uber
12.89
Mere
Francoise
noel
889

(注意:我在Mac OS X 10.8.4上并使用Python 2.7.3)


1
remove_accents旨在从Unicode字符串中删除重音符号。如果它传递了字节字符串,它将尝试使用将其转换为unicode字符串unicode(input_str)。这使用python的默认编码,即“ ascii”。由于您的文件使用UTF-8编码,因此将失败。第2行和第3行将python的默认编码更改为UTF-8,因此如您所知,它可以正常工作。另一种选择是传递remove_accentsunicode字符串:删除第2行和第3行,最后一行替换elementelement.decode("utf-8")。我测试了:可以。我将更新答案以使其更清楚。
MiniQuark 2013年

好编辑,好点。(另一方面,我意识到的真正问题是,我的数据文件显然是使用编码的iso-8859-1,很遗憾,我无法使用此功能!)
aseagram 2013年

aseagram:只需将“ utf-8”替换为“ iso-8859-1”,它就可以工作。如果您使用的是Windows,则应该改用“ cp1252”。
MiniQuark

BTW,reload(sys); sys.setdefaultencoding("utf-8")是一种可疑的黑客工具,有时建议用于Windows系统。有关详细信息,请参见stackoverflow.com/questions/28657010/…
下午18年

14

gensim.utils.deaccent(文本)Gensim -人类主题建模

'Sef chomutovskych komunistu dostal postou bily prasek'

另一个解决方案是unidecode

需要注意的是,用建议的解决方案unicodedata通常只在某些字符去掉口音(例如,它变成'ł''',而不是进入'l')。


1
deaccent仍然给予ł而不是l
lcieslak

你不需要安装NumPySciPy得到去除口音。
NunoAndré19年

感谢gensim参考!与unidecode相比(在速度或准确性方面)如何?
Etienne Kintzler,

3

一些语言结合了变音符号作为语言字母和重音符号来指定重音。

我认为更明确地指定要去除的折光度数更安全:

def strip_accents(string, accents=('COMBINING ACUTE ACCENT', 'COMBINING GRAVE ACCENT', 'COMBINING TILDE')):
    accents = set(map(unicodedata.lookup, accents))
    chars = [c for c in unicodedata.normalize('NFD', string) if c not in accents]
    return unicodedata.normalize('NFC', ''.join(chars))
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.