【问题标题】:What's the fastest way to strip and replace a document of high unicode characters using Python?使用 Python 剥离和替换高 unicode 字符文档的最快方法是什么?
【发布时间】:2011-02-20 16:56:21
【问题描述】:

我希望将大型文档中的所有高 unicode 字符(例如重音 Es、左右引号等)替换为低范围内的“正常”对应字符,例如常规的“E”和直引号。我需要经常在一个非常大的文档上执行此操作。我在这里看到了一个我认为可能是 perl 的例子:http://www.designmeme.com/mtplugins/lowdown.txt

在 Python 中是否有不使用 s.replace(...).replace(...).replace(...)... 的快速方法?我只尝试了几个要替换的字符,但文档剥离变得非常慢。

编辑,我的 unutbu 代码版本似乎不起作用:

# -*- coding: iso-8859-15 -*-
import unidecode
def ascii_map():
    data={}
    for num in range(256):
        h=num
        filename='x{num:02x}'.format(num=num)
        try:
            mod = __import__('unidecode.'+filename,
                             fromlist=True)
        except ImportError:
            pass
        else:
            for l,val in enumerate(mod.data):
                i=h<<8
                i+=l
                if i >= 0x80:
                    data[i]=unicode(val)
    return data

if __name__=='__main__':
    s = u'“fancy“fancy2'
    print(s.translate(ascii_map()))

【问题讨论】:

    标签: python unicode parsing ascii text-processing


    【解决方案1】:
    # -*- encoding: utf-8 -*-
    import unicodedata
    
    def shoehorn_unicode_into_ascii(s):
        return unicodedata.normalize('NFKD', s).encode('ascii','ignore')
    
    if __name__=='__main__':
        s = u"éèêàùçÇ"
        print(shoehorn_unicode_into_ascii(s))
        # eeeaucC
    

    注意,正如@Mark Tolonen 指出的那样,上面的方法删除了一些字符,例如 ß''“”。如果上面的代码截断了您希望翻译的字符,那么您可能必须使用字符串的translate 方法来手动修复这些问题。另一种选择是使用unidecode(参见J.F. Sebastian's answer)。

    当你有一个大的 unicode 字符串时,使用它的translate 方法会很多 比使用replace 方法快得多。

    编辑:unidecode 有更完整的 unicode 码点到 ascii 的映射。 但是,unidecode.unidecode 会逐个字符地循环字符串(在 Python 循环中),这比使用 translate 方法要慢。

    以下辅助函数使用unidecode 的数据文件和translate 方法来获得更好的速度,尤其是对于长字符串。

    在我对 1-6 MB 文本文件的测试中,使用 ascii_mapunidecode.unidecode 快大约 4-6 倍。

    # -*- coding: utf-8 -*-
    import unidecode
    def ascii_map():
        data={}
        for num in range(256):
            h=num
            filename='x{num:02x}'.format(num=num)
            try:
                mod = __import__('unidecode.'+filename,
                                 fromlist=True)
            except ImportError:
                pass
            else:
                for l,val in enumerate(mod.data):
                    i=h<<8
                    i+=l
                    if i >= 0x80:
                        data[i]=unicode(val)
        return data
    
    if __name__=='__main__':
        s = u"éèêàùçÇ"
        print(s.translate(ascii_map()))
        # eeeaucC
    

    Edit2:Rhubarb,如果 # -*- encoding: utf-8 -*- 导致 SyntaxError,请尝试 # -*- encoding: cp1252 -*-。声明什么编码取决于您的文本编辑器使用什么编码来保存文件。 Linux 倾向于使用 utf-8,而(似乎)Windows 倾向于使用 cp1252。

    【讨论】:

    • 不,NFKD 规范化将诸如 é 之类的字符分解为一个 e 和一个组合重音符号。使用忽略编码为 ascii 会留下 e 并删除非 ASCII 组合重音。问题是,并非所有非 ASCII 字符都分解为由 ASCII 和组合字符组成,因此像 ß''“” 这样的字符只是使用忽略来删除。
    • 我现在的问题是错误发生在另一个我没有源代码的库中。
    • @Rhubarb:如果当前的错误与这个问题无关,那么发布一个新问题如何?它会让你有机会更详细地描述问题,更多的人会看到你的帖子。
    • @Rhubarb:哎呀......看起来你已经这样做了。
    • @~unutbu,重新最近编辑:(1)我的答案中给出了一个已经制定的解决方案(2)unicode.translate 可以产生多个字符......“”“翻译表,它必须是 Unicode 序数到 Unicode 序数、Unicode 字符串或 None""" 的映射。 u"Gau\xdf".translate({0xdf: u"ss"}) 产生 u'Gauss'
    【解决方案2】:

    没有“高 ascii 字符”这样的东西。 ASCII 字符集仅限于范围 (128) 中的序数。

    除此之外,这是一个常见问题解答。这是one answer。一般来说,您应该熟悉 str.translate() 和 unicode.translate() - 对于单个字节/字符的多次替换非常方便。注意只提到 unicodedata.normalize() 噱头的答案;这只是解决方案的一部分。

    更新:正如 Mark Tolonen 所指出的,当前接受的答案会吹走没有分解的字符。似乎对unicode.translate() 的能力缺乏了解。它可以将一个字符翻译成多个字符。这是help(unicode.translate) 的输出:

    S.translate(table) -> unicode

    返回字符串 S 的副本,其中所有字符都已通过给定的转换表进行映射,该转换表必须是 Unicode 序数到 Unicode 序数、Unicode 字符串 或无的映射。未映射的字符保持不变。映射到 None 的字符被删除。

    这是一个例子:

    >>> u"Gau\xdf".translate({0xdf: u"ss"})
    u'Gauss'
    >>>
    

    这是我指出的解决方案中的修复表:

    CHAR_REPLACEMENT = {
        # latin-1 characters that don't have a unicode decomposition
        0xc6: u"AE", # LATIN CAPITAL LETTER AE
        0xd0: u"D",  # LATIN CAPITAL LETTER ETH
        0xd8: u"OE", # LATIN CAPITAL LETTER O WITH STROKE
        0xde: u"Th", # LATIN CAPITAL LETTER THORN
        0xdf: u"ss", # LATIN SMALL LETTER SHARP S
        0xe6: u"ae", # LATIN SMALL LETTER AE
        0xf0: u"d",  # LATIN SMALL LETTER ETH
        0xf8: u"oe", # LATIN SMALL LETTER O WITH STROKE
        0xfe: u"th", # LATIN SMALL LETTER THORN
        }
    

    这可以很容易地扩展以迎合在 cp1252 和兄弟姐妹中发现的花哨的引号和其他非 latin-1 字符。

    【讨论】:

    • 谢谢,我的意思是 unicode,但在这个晚上,这就是我得到的。
    【解决方案3】:

    如果~unubtu 建议的unicodedata.normalize() 不能解决问题,例如,如果您想更好地控制映射,您应该查看
    str.translate()
    与生成地图的实用程序 str.maketrans() 一起,str.translate 对于此类翻译既高效又方便。
    在 Python 2.x 和 unicode 字符串中,需要使用 unicode.translate() 而不是 str.translate() 和类似于下面代码 sn-p 中所示的技巧,代替maketrans() 的。 (感谢 John Machin 指出这一点!)

    这些方法在 Python 3.x 中也可用,例如 Python 3.1.2 documentation(出于某种原因,我已经注意到这在 Python 3.x 中可能已经改变)。当然,在 Python 3 下,所有字符串都是 unicode 字符串,但这是另一个问题。

    #Python 3.1
    >>> intab = 'àâçêèéïîôù'
    >>> outtab = 'aaceeeiiou'
    >>> tmap = str.maketrans(intab, outtab)
    >>> s = "à la fête de l'été, où il fait bon danser, les Français font les drôles"
    >>> s
    "à la fête de l'été, où il fait bon danser, les Français font les drôles"
    >>> s.translate(tmap)
    "a la fete de l'ete, ou il fait bon danser, les Francais font les droles"
    >>>
    
    
    #Python 2.6
    >>> intab = u'àâçêèéïîôù'
    >>> outtab = u'aaceeeiiou'
    >>> s = u"à la fête de l'été, où il fait bon danser, les Français font les drôles"
    >>> #note the trick to replace maketrans() since for unicode strings the translation
    >>> #     map expects integers (unicode ordinals) not characters.
    >>> tmap = dict(zip(map(ord, intab), map(ord, outtab))) 
    >>> s.translate(tmap)
    u"a la fete de l'ete, ou il fait bon danser, les Francais font les droles"
    >>>
    

    【讨论】:

    • 错了。在 Python2.x 中,使用 unicode.translate() 而不是 str.translate()
    • @John Machin:没错!感谢您注意到这一点。我进行了相应的编辑并为 3.1 和 2.6 添加了代码 sn-ps。
    【解决方案4】:

    这是处理 latin-1 字符的解决方案(基于 2003 usenet 线程):

    >>> accentstable = str.join("", map(chr, range(192))) + "AAAAAAACEEEEIIIIDNOOOOOxOUUUUYTsaaaaaaaceeeeiiiidnooooo/ouuuuyty"
    >>> import string
    >>> s = u"éèêàùçÇ"
    >>> print string.translate(s.encode('latin1', 'ignore'), accentstable)
    eeeaucC
    

    有些映射并不完美,例如Thorn 映射到 T 而不是 Th,但它的工作还算可以接受。

    【讨论】:

      【解决方案5】:

      我相信unicodedata 不适用于花哨的报价。在这种情况下,您可以使用 Unidecode

      import unidecode
      print unidecode.unidecode(u"ß‘’“”")
      # -> ss''""
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-06-14
        • 2011-01-07
        • 1970-01-01
        • 1970-01-01
        • 2019-06-23
        • 1970-01-01
        • 2011-01-12
        • 2019-07-18
        相关资源
        最近更新 更多