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

我在网上找到了一个优雅的方法(在Java中):

将Unicode字符串转换为它的长规范化形式(使用单独的字符表示字母和变音符) 删除所有Unicode类型为“变音符”的字符。

我需要安装一个库,如pyICU或这是可能的Python标准库?那么python3呢?

重要提示:我希望避免使用从重音字符到非重音对应字符的显式映射的代码。


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

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

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

编辑2:remove_accent需要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)

这个怎么样:

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

字符类别“Mn”代表Nonspacing_Mark,它类似于MiniQuark回答中的unicodedata. combined(我没有想到unicodedata. combined,但它可能是更好的解决方案,因为它更显式)。

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


unidcode是正确的答案。它将任何unicode字符串音译为最接近的ascii文本表示形式。

例子:

>>> from unidecode import unidecode
>>> unidecode('kožušček')
'kozuscek'
>>> unidecode('北亰')
'Bei Jing '
>>> unidecode('François')
'Francois'

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

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

这是我能想到的最优雅的方式(亚历克西斯在本页的评论中提到过),尽管我不认为它真的很优雅。 事实上,正如评论中指出的那样,这更像是一种黑客,因为Unicode名称实际上只是名称,它们不能保证一致或任何东西。

仍然有一些特殊的字母没有被处理,比如反转字母和倒装字母,因为它们的unicode名称不包含'WITH'。这取决于你想做什么。我有时需要重音剥离来实现字典排序顺序。

编辑注:

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


对于@ mini夸克的回答:

我试图读取一个csv文件,它是一半法语(包含重音),也有一些字符串,最终将成为整数和浮动。 作为测试,我创建了一个test.txt文件,如下所示:

M, Montré艾尔,关于12.89太阳è弗兰çoise,不889,ël

我必须包括第2行和第3行才能让它工作(这是我在python ticket中找到的),以及加入@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)


实际上,我在项目兼容的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'

有些语言将变音符组合成语言字母,并将重音变音符组合成重音。

我认为更安全的做法是明确指定你想要剥离的变量:

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))

Gensim .utils.deaccent(text)来自Gensim -人类主题建模:

'Sef chomutovskych komunistu dostal postou bily prasek'

另一种解决方案是unicode。

请注意,建议的unicodedata解决方案通常只删除某些字符中的重音(例如,它将“ova”变成了“”,而不是“l”)。


import unicodedata
from random import choice

import perfplot
import regex
import text_unidecode


def remove_accent_chars_regex(x: str):
    return regex.sub(r'\p{Mn}', '', unicodedata.normalize('NFKD', x))


def remove_accent_chars_join(x: str):
    # answer by MiniQuark
    # https://stackoverflow.com/a/517974/7966259
    return u"".join([c for c in unicodedata.normalize('NFKD', x) if not unicodedata.combining(c)])


perfplot.show(
    setup=lambda n: ''.join([choice('Málaga François Phút Hơn 中文') for i in range(n)]),
    kernels=[
        remove_accent_chars_regex,
        remove_accent_chars_join,
        text_unidecode.unidecode,
    ],
    labels=['regex', 'join', 'unidecode'],
    n_range=[2 ** k for k in range(22)],
    equality_check=None, relative_to=0, xlabel='str len'
)

如果您希望获得类似Elasticsearch的ascii折叠过滤器的功能,您可能需要考虑fold-to-ascii,这是[本身]…

Apache Lucene ASCII折叠过滤器的Python端口,它将字母、数字和符号Unicode字符转换为不属于前127个ASCII字符的字符(“基本拉丁”Unicode块),如果它们存在的话。

下面是上面提到的一个例子:

from fold_to_ascii import fold
s = u'Astroturf® paté'
fold(s)
> u'Astroturf pate'
fold(s, u'?')
> u'Astroturf? pate'

编辑:fold_to_ascii模块似乎可以很好地规范化基于拉丁的字母;然而,不可映射的字符将被删除,这意味着该模块将减少中文文本,例如,空字符串。如果您想保留中文、日语和其他Unicode字母,可以考虑使用上面@mo-han的remove_accent_chars_regex实现。


在我看来,所提出的解决办法不应该是公认的答案。原来的问题是要求去掉重音,所以正确的答案应该只去掉重音,而不是去掉重音加上其他未指明的变化。

简单地观察这段代码的结果,这是公认的答案。我把“Málaga”改成了“Málagueña”:

accented_string = u'Málagueña'
# accented_string is of type 'unicode'
import unidecode
unaccented_string = unidecode.unidecode(accented_string)
# unaccented_string contains 'Malaguena'and is of type 'str'

有一个额外的更改(ñ -> n),这在OQ中没有请求。

一个简单的函数,以较低的形式完成所请求的任务:

def f_remove_accents(old):
    """
    Removes common accent characters, lower form.
    Uses: regex.
    """
    new = old.lower()
    new = re.sub(r'[àáâãäå]', 'a', new)
    new = re.sub(r'[èéêë]', 'e', new)
    new = re.sub(r'[ìíîï]', 'i', new)
    new = re.sub(r'[òóôõö]', 'o', new)
    new = re.sub(r'[ùúûü]', 'u', new)
    return new

下面是一个简短的函数,它去掉了变音符,但保留了非拉丁字符。大多数情况(例如,"à" -> "a")由unicodedata(标准库)处理,但一些情况(例如,"æ" -> "ae")依赖于给定的并行字符串。

Code

from unicodedata import combining, normalize

LATIN = "ä  æ  ǽ  đ ð ƒ ħ ı ł ø ǿ ö  œ  ß  ŧ ü "
ASCII = "ae ae ae d d f h i l o o oe oe ss t ue"

def remove_diacritics(s, outliers=str.maketrans(dict(zip(LATIN.split(), ASCII.split())))):
    return "".join(c for c in normalize("NFD", s.lower().translate(outliers)) if not combining(c))

NB。默认参数异常值只计算一次,并不意味着由调用方提供。

目的使用

作为一个键,以更“自然”的顺序对字符串列表进行排序:

sorted(['cote', 'coteau', "crottez", 'crotté', 'côte', 'côté'], key=remove_diacritics)

输出:

['cote', 'côte', 'côté', 'coteau', 'crotté', 'crottez']

如果字符串混合了文本和数字,您可能会对使用我在其他地方给出的string_to_pairs()函数组合remove_diacritics()感兴趣。

测试

为了确保行为符合你的需求,看看下面的程序图:

examples = [
    ("hello, world", "hello, world"),
    ("42", "42"),
    ("你好,世界", "你好,世界"),
    (
        "Dès Noël, où un zéphyr haï me vêt de glaçons würmiens, je dîne d’exquis rôtis de bœuf au kir, à l’aÿ d’âge mûr, &cætera.",
        "des noel, ou un zephyr hai me vet de glacons wuermiens, je dine d’exquis rotis de boeuf au kir, a l’ay d’age mur, &caetera.",
    ),
    (
        "Falsches Üben von Xylophonmusik quält jeden größeren Zwerg.",
        "falsches ueben von xylophonmusik quaelt jeden groesseren zwerg.",
    ),
    (
        "Љубазни фењерџија чађавог лица хоће да ми покаже штос.",
        "љубазни фењерџија чађавог лица хоће да ми покаже штос.",
    ),
    (
        "Ljubazni fenjerdžija čađavog lica hoće da mi pokaže štos.",
        "ljubazni fenjerdzija cadavog lica hoce da mi pokaze stos.",
    ),
    (
        "Quizdeltagerne spiste jordbær med fløde, mens cirkusklovnen Walther spillede på xylofon.",
        "quizdeltagerne spiste jordbaer med flode, mens cirkusklovnen walther spillede pa xylofon.",
    ),
    (
        "Kæmi ný öxi hér ykist þjófum nú bæði víl og ádrepa.",
        "kaemi ny oexi her ykist þjofum nu baedi vil og adrepa.",
    ),
    (
        "Glāžšķūņa rūķīši dzērumā čiepj Baha koncertflīģeļu vākus.",
        "glazskuna rukisi dzeruma ciepj baha koncertfligelu vakus.",
    )
]

for (given, expected) in examples:
    assert remove_diacritics(given) == expected

Case-preserving变体

LATIN = "ä  æ  ǽ  đ ð ƒ ħ ı ł ø ǿ ö  œ  ß  ŧ ü  Ä  Æ  Ǽ  Đ Ð Ƒ Ħ I Ł Ø Ǿ Ö  Œ  SS Ŧ Ü "
ASCII = "ae ae ae d d f h i l o o oe oe ss t ue AE AE AE D D F H I L O O OE OE SS T UE"

def remove_diacritics(s, outliers=str.maketrans(dict(zip(LATIN.split(), ASCII.split())))):
    return "".join(c for c in normalize("NFD", s.translate(outliers)) if not combining(c))


这里已经有很多答案,但之前没有考虑过:使用sklearn

from sklearn.feature_extraction.text import strip_accents_ascii, strip_accents_unicode

accented_string = u'Málagueña®'

print(strip_accents_unicode(accented_string)) # output: Malaguena®
print(strip_accents_ascii(accented_string)) # output: Malaguena

如果您已经在使用sklearn处理文本,这一点特别有用。这些是由CountVectorizer等类内部调用的函数,用于规范化字符串:当使用strip_accent ='ascii'时,则调用strip_accents_ascii;当使用strip_accent ='unicode'时,则调用strip_accents_unicode。

更多的细节

最后,考虑文档字符串中的这些细节:

Signature: strip_accents_ascii(s)
Transform accentuated unicode symbols into ascii or nothing

Warning: this solution is only suited for languages that have a direct
transliteration to ASCII symbols.

and

Signature: strip_accents_unicode(s)
Transform accentuated unicode symbols into their simple counterpart

Warning: the python-level loop and join operations make this
implementation 20 times slower than the strip_accents_ascii basic
normalization.