Какой самый быстрый способ вырезать и заменить документ с высокими символами Юникода, используя Python? - PullRequest
5 голосов
/ 18 мая 2010

Я хочу заменить из большого документа все символы высокого Юникода, такие как 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()))

Ответы [ 5 ]

7 голосов
/ 18 мая 2010
# -*- 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 имеет более полное отображение кодовых точек юникода в 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.

4 голосов
/ 18 мая 2010

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

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

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

S.translate (таблица) -> Юникод

Возвращает копию строки S, в которой все символы были отображены через заданную таблицу перевода, которая должна быть отображением ординалов Юникода в ординалы Юникода, Строки Юникода или Нет. Несопоставленные символы остаются нетронутыми. Символы, сопоставленные с 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 голосов
/ 20 мая 2010

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

import unidecode
print unidecode.unidecode(u"ß‘’“”")
# -> ss''""
1 голос
/ 18 мая 2010

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

Эти методы также доступны в 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"
>>>
0 голосов
/ 18 мая 2010

Вот решение, которое обрабатывает символы латиницы-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, но этосносная работа.

...