在 Python unicode 字符串中删除重音符号(规范化)的最佳方法是什么?

2024-11-22 08:48:00
admin
原创
262
摘要:问题描述:我在 Python 中有一个 Unicode 字符串,我想删除所有重音符号(变音符号)。我在网上找到了一种优雅的方法来实现这一点(用Java):将 Unicode 字符串转换为其长规范化形式(字母和变音符号使用单独的字符)删除所有 Unicode 类型为“变音符号”的字符。我是否需要安装诸如 pyI...

问题描述:

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

我在网上找到了一种优雅的方法来实现这一点(用Java):

  1. 将 Unicode 字符串转换为其长规范化形式(字母和变音符号使用单独的字符)

  2. 删除所有 Unicode 类型为“变音符号”的字符。

我是否需要安装诸如 pyICU 之类的库,或者仅使用 Python 标准库就可以实现这一点?那么 Python 3 呢?

重要提示:我想避免使用从重音字符到非重音字符的明确映射代码。


解决方案 1:

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

例子:

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

解决方案 2:

这个怎么样:

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.combining(我没有想到unicodedata.combining,但它可能是更好的解决方案,因为它更明确)。

请记住,这些操作可能会显著改变文本的含义。重音符号、变音符号等不是“装饰”。

解决方案 3:

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

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字符串,而不是字节字符串。如果您有字节字符串,则必须将其解码为 un​​icode 字符串,如下所示:

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)

解决方案 4:

实际上,我从事与 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'

解决方案 5:

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

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 在本页的评论中也提到过),尽管我认为它确实不是很优雅。事实上,正如评论中指出的那样,它更像是一种 hack,因为 Unicode 名称实际上只是名称,它们不保证一致性或其他任何东西。

仍有一些特殊字母无法通过此方法处理,例如旋转和倒置字母,因为它们的 Unicode 名称不包含“WITH”。这取决于你想做什么。我有时需要去除重音来实现字典排序顺序。

编辑说明:

吸收了评论中的建议(处理查找错误、Python-3 代码)。

解决方案 6:

在我看来,所提出的解决方案不应该被接受。原始问题要求删除重音符号,因此正确答案应该只删除重音符号,而不是加上其他未指定的更改。

只需观察此代码的结果,即可接受的答案。我已将“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

解决方案 7:

回应@MiniQuark 的回答:

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

蒙特利尔, über, 12.89, Mère, Françoise, noël, 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)

解决方案 8:

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

'Sef chomutovskych komunistu dostal postou bily prasek'

另一个解决方案是unidecode。

请注意,使用unicodedata的建议解决方案通常仅删除某些字符中的重音符号(例如,它变成'ł'''而不是'l')。

解决方案 9:

性能图

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

解决方案 10:

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

代码

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

注意:默认参数outliers只计算一次,并不意味着由调用者提供。

预期用途

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

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

输出:

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

如果您的字符串混合了文本和数字,您可能有兴趣使用我在其他地方remove_diacritics()提供的函数进行组合。string_to_pairs()

测试

为了确保该行为满足您的需求,请查看下面的全字母表:

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

保留大小写变体

LATIN = "ä  æ  ǽ  đ ð ƒ ħ ı ł ø ǿ ö  œ  ß  ŧ ü  Ä  Æ  Ǽ  Đ Ð Ƒ Ħ I Ł Ø Ǿ Ö  Œ  ẞ  Ŧ Ü "
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))

解决方案 11:

有些语言将变音符号作为语言字母,将重音变音符号结合起来以指定重音。

我认为明确指定要剥离的变音符号更为安全:

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

解决方案 12:

这里已经有很多答案了,但是之前没有考虑过这一点:使用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_accents='ascii'then ,使用时调用 then。strip_accents_ascii`strip_accents='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.

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.

解决方案 13:

如果您希望获得类似于 Elasticsearchasciifolding过滤器的功能,您可能需要考虑fold-to-ascii,它[本身]...

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

以下是上述页面的一个示例:

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 字母表,请考虑使用remove_accent_chars_regex上面的 @mo-han 的实现。

解决方案 14:

我想到了这个(特别是对于拉丁字母 - 语言目的)

import string
from functools import lru_cache

import unicodedata


# This can improve performance by avoiding redundant computations when the function is
# called multiple times with the same arguments.
@lru_cache
def lookup(
    l: str, case_sens: bool = True, replace: str = "", add_to_printable: str = ""
):
    r"""
    Look up information about a character and suggest a replacement.

    Args:
        l (str): The character to look up.
        case_sens (bool, optional): Whether to consider case sensitivity for replacements. Defaults to True.
        replace (str, optional): The default replacement character when not found. Defaults to ''.
        add_to_printable (str, optional): Additional uppercase characters to consider as printable. Defaults to ''.

    Returns:
        dict: A dictionary containing the following information:
            - 'all_data': A sorted list of words representing the character name.
            - 'is_printable_letter': True if the character is a printable letter, False otherwise.
            - 'is_printable': True if the character is printable, False otherwise.
            - 'is_capital': True if the character is a capital letter, False otherwise.
            - 'suggested': The suggested replacement for the character based on the provided criteria.
    Example:
        sen = "Montréal, über, 12.89, Mère, Françoise, noël, 889"
        norm = ''.join([lookup(k, case_sens=True, replace='x', add_to_printable='')['suggested'] for k in sen])
        print(norm)
        #########################
        sen2 = 'kožušček'
        norm2 = ''.join([lookup(k, case_sens=True, replace='x', add_to_printable='')['suggested'] for k in sen2])
        print(norm2)
        #########################

        sen3="Falsches Üben von Xylophonmusik quält jeden größeren Zwerg."
        norm3 = ''.join([lookup(k, case_sens=True, replace='x', add_to_printable='')['suggested'] for k in sen3]) # doesn't preserve ü - ue ...
        print(norm3)
        #########################
        sen4 = "cætera"
        norm4 = ''.join([lookup(k, case_sens=True, replace='x', add_to_printable='ae')['suggested'] for k in
                         sen4])  
        print(norm4)


        # Montreal, uber, 12.89, Mere, Francoise, noel, 889
        # kozuscek
        # Falsches Uben von Xylophonmusik qualt jeden groseren Zwerg.
        # caetera
    """
    # The name of the character l is retrieved using the unicodedata.name()
    # function and split into a list of words and sorted by len (shortest is the wanted letter)
    v = sorted(unicodedata.name(l).split(), key=len)
    sug = replace
    stri_pri = string.printable + add_to_printable.upper()
    is_printable_letter = v[0] in stri_pri
    is_printable = l in stri_pri
    is_capital = "CAPITAL" in v
    # Depending on the values of the boolean variables, the variable sug may be
    # updated to suggest a replacement for the character l. If the character is a printable letter,
    # the suggested replacement is set to the first word in the sorted list of names (v).
    # If case_sens is True and the character is a printable letter but not a capital,
    # the suggested replacement is set to the lowercase version of the first word in v.
    # If the character is printable, the suggested replacement is set to the character l itself.
    if is_printable_letter:
        sug = v[0]

        if case_sens:
            if not is_capital:
                sug = v[0].lower()
    elif is_printable:
        sug = l
    return {
        "all_data": v,
        "is_printable_letter": is_printable_letter,
        "is_printable": is_printable,
        "is_capital": is_capital,
        "suggested": sug,
    }

我想到的另一种解决方案也是基于查找字典和 Numba,但源代码太大,无法在此处发布。这是 GitHub 链接:https://github.com/hansalemaos/charchef

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   3868  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   2711  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Freshdesk、ClickUp、nTask、Hubstaff、Plutio、Productive、Targa、Bonsai、Wrike。在当今快速变化的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多企业在项目管理过程中面临着诸多痛点,如任务分配不...
项目管理系统   44  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Monday、TeamGantt、Filestage、Chanty、Visor、Smartsheet、Productive、Quire、Planview。在当今快速变化的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多项目经理和团队在管理复杂项目时,常...
开源项目管理工具   42  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Smartsheet、GanttPRO、Backlog、Visor、ResourceGuru、Productive、Xebrio、Hive、Quire。在当今快节奏的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多企业在选择项目管理工具时常常面临困惑:...
项目管理系统   44  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用