Какой самый быстрый способ снять и заменить документ с высокими символами Юникода с помощью Python?

Я хочу заменить из большого документа все высокие символы юникода, такие как акцентированные Es, левые и правые кавычки и т.д., с "нормальными" аналогами в низком диапазоне, такими как обычный "E" и прямой кавычки. Мне нужно довольно часто выполнять это на очень большом документе. Я вижу пример этого в том, что, на мой взгляд, может быть perl: http://www.designmeme.com/mtplugins/lowdown.txt

Есть ли быстрый способ сделать это в Python без использования s.replace(...). replace (...). replace (...)...? Я пробовал это на нескольких символах для замены, и снятие документа стало очень медленным.

EDIT, моя версия кода 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()))

Ответ 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. Ответ Себастьяна).

Когда у вас есть большая строка юникода, использование метода translate будет много гораздо быстрее, чем при использовании метода replace.

Изменить: unidecode имеет более полное сопоставление кодов Unicode для ascii. Тем не менее, unidecode.unidecode проходит через строковый символ по символу (в цикле Python), который медленнее, чем метод translate.

Следующая вспомогательная функция использует файлы данных unidecode и метод translate для достижения более высокой скорости, особенно для длинных строк.

В моих тестах с текстовыми файлами 1-6 Мбайт использование ascii_map примерно в 4-6 раз быстрее, чем unidecode.unidecode.

# -*- 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.

Ответ 2

Нет такой вещи, как "высокий символ ascii". Набор символов ASCII ограничен порядковым номером в диапазоне (128).

В стороне, это FAQ. Здесь один ответ. В общем, вам следует ознакомиться с str.translate() и unicode.translate() - очень удобно для нескольких подстановок одиночных байтов/символов. Остерегайтесь ответов, в которых упоминается только трюк unicodedata.normalize(); что только одна часть решения.

Обновление: принятый в настоящее время ответ удаляет символы, которые не имеют разложения, как указал Марк Толонен. Кажется, что нет знания того, чем способна unicode.translate(). Он МОЖЕТ перевести один символ на несколько символов. Вот результат от help(unicode.translate):

S.translate(table) → unicode

Верните копию строки S, где все символы были сопоставлены с помощью данной таблицы перевода, которая должна быть отображением ордеров Unicode в ординалы Unicode, Unicode string или 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
    }

Это можно легко расширить, чтобы удовлетворить причудливые кавычки и другие символы, отличные от латинского-1, найденные в cp1252 и братьях и сестрах.

Ответ 3

Я считаю, что unicodedata не работает для причудливых кавычек. Вы можете использовать Unidecode в этом случае:

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

Ответ 4

Если unicodedata.normalize(), как предложено ~unubtu, не делает трюк, для Например, если вы хотите больше контролировать отображение, вы должны посмотреть в str.translate()
наряду с str.maketrans(), утилитой для создания таблицы карт, str.translate эффективен и удобен для такого типа перевода. В Python 2.x и для строк unicode нужно использовать unicode.translate(), а не str.translate(), и трюк, подобный тому, который показан в фрагменте кода ниже, вместо maketrans(). (спасибо Джону Мачину за это!)

Эти методы также доступны в Python 3.x, например, Документация Python 3.1.2 (по какой-то причине я сделал что это может измениться в 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"
>>>

Ответ 5

Здесь решение, которое обрабатывает символы латинского-1 (на основе потока usenet 2003):

>>> 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, но он выполняет терпимую работу.