使用Python剥离和替换高Unicode字符文档的最快方法是什么?

5 投票
5 回答
3645 浏览
提问于 2025-04-15 22:50

我想在一个很大的文档中,把所有的高Unicode字符,比如带重音的E、左右引号等,替换成“正常”的低Unicode字符,比如普通的'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()))

5 个回答

3

我觉得 unicodedata 对于花哨的引号是没用的。在这种情况下,你可以使用 Unidecode

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

其实并没有所谓的“高ASCII字符”。ASCII字符集的范围是从0到127。

说到这个,这个问题其实很常见。这里有一个答案。一般来说,你应该了解一下str.translate()和unicode.translate()这两个函数——它们在处理多个单字节/字符替换时非常有用。要小心那些只提到unicodedata.normalize()的回答;那只是解决方案的一部分。

更新:目前被接受的答案会删除那些没有分解的字符,正如Mark Tolonen所指出的。似乎大家对unicode.translate()的功能了解得不够。它可以把一个字符转换成多个字符。以下是help(unicode.translate)的输出:

S.translate(table) -> unicode

返回字符串S的一个副本,所有字符都通过给定的翻译表进行映射,这个翻译表必须是Unicode编码到Unicode编码、Unicode字符串或None的映射。未映射的字符保持不变。映射到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字符。

8
# -*- 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的回答)。

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

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

以下这个辅助函数使用了unidecode的数据文件,并结合translate方法,以获得更好的速度,特别是对于长字符串。

在我对1-6 MB文本文件的测试中,使用ascii_map的速度大约是unidecode.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

编辑2:Rhubarb,如果# -*- encoding: utf-8 -*-导致了语法错误,试试# -*- encoding: cp1252 -*-。声明什么编码取决于你的文本编辑器用什么编码来保存文件。Linux通常使用utf-8,而(似乎)Windows通常使用cp1252。

撰写回答