2013-11-19 3 views
159

Мне нужно заменить все символы, отличные от ASCII (\ x00- \ x7F) пробелом. Я удивлен, что в Python это не так легко, если я ничего не пропустил. Следующая функция просто удаляет все символы не-ASCII:Заменить не-ASCII-символы одним пробелом

def remove_non_ascii_1(text): 

    return ''.join(i for i in text if ord(i)<128) 

И это один заменяет не-ASCII символы с количеством пробелов как в количество байт в коде символа (т.е. символа заменяются 3 пробела):

def remove_non_ascii_2(text): 

    return re.sub(r'[^\x00-\x7F]',' ', text) 

Как я могу заменить все символы не-ASCII с одним пробелом?

OfthemyriadofsimilarSOquestions, noneaddresscharacterreplacementasopposedtostripping, and дополнительно рассмотреть все символы не-ASCII не конкретный характер.

+27

ничего себе, вы действительно взяли хороших усилий по показать так много ссылок. +1, как только день возобновится! –

+3

Вы, кажется, пропустили это http://stackoverflow.com/questions/1342000/how-to-replace-non-ascii-characters-in-string – Stuart

+0

Мне интересно увидеть пример ввода, который имеет проблемы. – dstromberg

ответ

155

Ваше выражение ''.join(): фильтрация, удаление чего-либо не-ASCII; Вы можете использовать условное выражение вместо:

return ''.join([i if ord(i) < 128 else ' ' for i in text]) 

Это обрабатывает символы один за другим, и будет по-прежнему использовать один пробел на символ заменен.

Ваше регулярное выражение должно просто заменить последовательных не-ASCII символы с пробелом:

re.sub(r'[^\x00-\x7F]+',' ', text) 

Обратите внимание на + там.

+0

Почему список? Что случилось с выражением генератора? – dstromberg

+13

@ dstromberg: медленнее; 'str.join()' * нужен * список (он будет передавать значения дважды), а выражение генератора сначала будет преобразовано в одно. Предоставление этого понимания списка происходит просто быстрее. См. [Этот пост] (http://stackoverflow.com/a/9061024). –

+1

Первый фрагмент кода добавит несколько пробелов на символ, если вы будете кормить его байтовой строкой UTF-8. –

16

Для символов обработки, использовать строки Unicode:

PythonWin 3.3.0 (v3.3.0:bd8afb90ebf2, Sep 29 2012, 10:57:17) [MSC v.1600 64 bit (AMD64)] on win32. 
>>> s='ABC马克def' 
>>> import re 
>>> re.sub(r'[^\x00-\x7f]',r' ',s) # Each char is a Unicode codepoint. 
'ABC def' 
>>> b = s.encode('utf8') 
>>> re.sub(rb'[^\x00-\x7f]',rb' ',b) # Each char is a 3-byte UTF-8 sequence. 
b'ABC  def' 

Но заметьте, вы все еще есть проблемы, если ваша строка содержит разложенные символы Unicode (отдельный символ и комбинирование знаки ударения, например):

>>> s = 'mañana' 
>>> len(s) 
6 
>>> import unicodedata as ud 
>>> n=ud.normalize('NFD',s) 
>>> n 
'mañana' 
>>> len(n) 
7 
>>> re.sub(r'[^\x00-\x7f]',r' ',s) # single codepoint 
'ma ana' 
>>> re.sub(r'[^\x00-\x7f]',r' ',n) # only combining mark replaced 
'man ana' 
+0

Спасибо, это важное замечание. Если вы найдете логический способ обработки случая совмещения меток, я бы с радостью добавил щедрость на вопрос. Я полагаю, что просто удалить комбинационную метку, но оставив только несовместимый персонаж, было бы лучше. – dotancohen

+0

Частичное решение заключается в использовании 'ud.normalize ('NFC', s)' для объединения меток, но не все комбинации комбинаций представлены одиночными кодовыми точками. Вам понадобится более разумное решение, рассматривающее 'ud.category()' персонажа. –

+1

@dotancohen: в Unicode есть понятие «воспринимаемый пользователем персонаж», который может охватывать несколько кодов Unicode. '\ X' (eXtended grapheme cluster) regex (поддерживается модулем' regex') позволяет выполнять итерацию по таким символам (примечание: [*] графемы не обязательно сочетают последовательности символов, а объединение последовательностей символов не обязательно графемы "*] (http://unicode.org/faq/char_combmark.html)). – jfs

25

для вас получить наиболее Alike представление исходной строки я рекомендую the unidecode module:

from unidecode import unidecode 
def remove_non_ascii(text): 
    return unidecode(unicode(text, encoding = "utf-8")) 

Затем вы можете использовать его в строке:

remove_non_ascii("Ceñía") 
Cenia 
+0

интересное предложение, но предполагает, что пользователь желает, чтобы не ascii стал тем, чем являются правила для unidecode. Это, однако, ставит вопрос о том, почему они настаивают на пробелах, возможно, заменить другого персонажа? – jxramos

+0

Спасибо, это хороший ответ.Это не работает с целью этого вопроса, потому что большинство данных, с которыми я имею дело, не имеют ASCII-подобного представления. Например, 'דותן'. Однако, в общем смысле, это здорово, спасибо! – dotancohen

+0

Да, я знаю, что это не работает для этого вопроса, но я приземлился здесь, пытаясь решить эту проблему, поэтому я решил поделиться своим решением с моей собственной проблемой, которая, как мне кажется, очень распространена для людей как @dotancohen, кто иметь дело с символами не-ascii все время. –

4

Что об этом?

def replace_trash(unicode_string): 
    for i in range(0, len(unicode_string)): 
     try: 
      unicode_string[i].encode("ascii") 
     except: 
       #means it's non-ASCII 
       unicode_string=unicode_string[i].replace(" ") #replacing it with a single space 
    return unicode_string 
+0

Хотя это довольно неэлегантно, оно очень читаемо. Спасибо. – dotancohen

+0

+1 для обработки unicode ... @dotancohen IMNSHO «читаемый» подразумевает «практический», который добавляет к «изящному», поэтому я бы сказал «немного неэлегантный» – qneill

5

Если символ замены может быть '?'Вместо пробела, то я бы предложил result = text.encode('ascii', 'replace').decode():

"""Test the performance of different non-ASCII replacement methods.""" 


import re 
from timeit import timeit 


# 10_000 is typical in the project that I'm working on and most of the text 
# is going to be non-ASCII. 
text = 'Æ' * 10_000 


print(timeit(
    """ 
result = ''.join([c if ord(c) < 128 else '?' for c in text]) 
    """, 
    number=1000, 
    globals=globals(), 
)) 

print(timeit(
    """ 
result = text.encode('ascii', 'replace').decode() 
    """, 
    number=1000, 
    globals=globals(), 
)) 

Результаты:

0.7208260721400134 
0.009975979187503592 
+0

Замените? при необходимости, с другим символом или пробелом, и вы все равно будете быстрее. – Moritz

0

как родной и эффективный подход, вам не нужно использовать ord или любой цикл над персонажами , Просто закодируйте с помощью ascii и проигнорируйте ошибки.

Далее будет просто удалить не-ASCII символы:

new_string = old_string.encode('ascii',errors='ignore') 

Теперь, если вы хотите, чтобы заменить удаленные символы просто сделайте следующее:

final_string = new_string + b' ' * (len(old_string) - len(new_string)) 
Смежные вопросы