Как я могу удалить символы, отличные от ASCII, но оставить точки и пробелы с помощью Python?

101

Я работаю с файлом .txt. Мне нужна строка текста из файла без символов, отличных от ASCII. Однако я хочу оставить пробелы и точки. В настоящее время я их тоже снимаю. Вот код:

def onlyascii(char):
    if ord(char) < 48 or ord(char) > 127: return ''
    else: return char

def get_my_string(file_path):
    f=open(file_path,'r')
    data=f.read()
    f.close()
    filtered_data=filter(onlyascii, data)
    filtered_data = filtered_data.lower()
    return filtered_data

Как мне изменить onlyascii (), чтобы оставить пробелы и точки? Думаю, это не так уж сложно, но я не могу понять.

Alexwlchan
источник
Спасибо (искренне) за разъяснения, Джон. Я понял, что пробелы и точки - это символы ASCII. Однако я случайно удалил их оба, пытаясь удалить только символы, отличные от ASCII. Я понимаю, как мой вопрос мог подразумевать иное.
@PoliticalEconomist: Ваша проблема все еще очень недооценена. Смотрите мой ответ.
Джон Мачин

Ответы:

188

Вы можете отфильтровать все символы из строки, которые нельзя распечатать, используя string.printable , например:

>>> s = "some\x00string. with\x15 funny characters"
>>> import string
>>> printable = set(string.printable)
>>> filter(lambda x: x in printable, s)
'somestring. with funny characters'

string.printable на моей машине содержит:

0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~ \t\n\r\x0b\x0c

РЕДАКТИРОВАТЬ: на Python 3 фильтр вернет итерацию. Правильный способ вернуть строку:

''.join(filter(lambda x: x in printable, s))
терраса
источник
2
что случилось с теми печатными символами, которые ниже порядкового номера 48?
Хоакин
38
Единственная проблема с использованием filterзаключается в том, что он возвращает итерацию. Если вам нужна тетива (как я сделал , потому что мне нужно было это делать , когда сжатие списка) , то сделать это: ''.join(filter(lambda x: x in string.printable, s).
cjbarth 05
5
@cjbarth - комментарий специфичен для Python 3, но очень полезен. Спасибо!
Undershock
7
Почему бы не использовать регулярное выражение: re.sub(r'[^\x00-\x7f]',r'', your-non-ascii-string). См. Эту ветку stackoverflow.com/a/20079244/658497
Ноам Манос
1
@NoamManos для меня это было в 4-5 раз быстрее, чем объединение ... фильтр ... лямбда-решение, спасибо.
artfulrobot
95

Простой способ перейти на другой кодек - использовать encode () или decode (). В вашем случае вы хотите преобразовать в ASCII и игнорировать все символы, которые не поддерживаются. Например, шведская буква å не является символом ASCII:

    >>>s = u'Good bye in Swedish is Hej d\xe5'
    >>>s = s.encode('ascii',errors='ignore')
    >>>print s
    Good bye in Swedish is Hej d

Редактировать:

Python3: стр -> байты -> стр.

>>>"Hej då".encode("ascii", errors="ignore").decode()
'hej d'

Python2: юникод -> стр -> юникод

>>> u"hej då".encode("ascii", errors="ignore").decode()
u'hej d'

Python2: str -> unicode -> str (декодировать и кодировать в обратном порядке)

>>> "hej d\xe5".decode("ascii", errors="ignore").encode()
'hej d'
Zweedeend
источник
16
Я получаюUnicodeDecodeError: 'ascii' codec can't decode byte 0xc2 in position 27
Xodarap777
2
Я получил эту ошибку, когда вставил фактический символ Юникода в строку с помощью копирования и вставки. Когда вы указываете строку как u'string ', кодировка работает правильно.
Бен Лиянаге
2
Работает только на Py3, но элегантно.
gaborous
7
Для тех, кто получает ту же ошибку, что и @ Xodarap777: сначала вы должны .decode () строку и только после этого кодировать. Напримерs.decode('utf-8').encode('ascii', errors='ignore')
Spc_555
30

Согласно @artfulrobot, это должно быть быстрее, чем фильтр и лямбда:

re.sub(r'[^\x00-\x7f]',r'', your-non-ascii-string) 

См. Дополнительные примеры здесь http://stackoverflow.com/questions/20078816/replace-non-ascii-characters-with-a-single-space/20079244#20079244

Ноам Манос
источник
1
Это решение отвечает на заданный вопрос OP, но имейте в виду, что оно не удалит непечатаемые символы, которые включены в ASCII, что, как я думаю, было тем, что OP намеревался спросить.
Данило Соуза Морайнш
6

Ваш вопрос неоднозначный; первые два предложения, взятые вместе, означают, что вы считаете, что пробел и «точка» не являются символами ASCII. Это неверно. Все символы, такие как ord (char) <= 127, являются символами ASCII. Например, ваша функция исключает эти символы! "# $% & \ '() * +, -. /, Но включает несколько других, например [] {}.

Пожалуйста, сделайте шаг назад, немного подумайте и отредактируйте свой вопрос, чтобы сообщить нам, что вы пытаетесь сделать, не упоминая слово ASCII, и почему вы думаете, что такие символы, как ord (char)> = 128, игнорируются. Также: какая версия Python? Какая кодировка ваших входных данных?

Обратите внимание, что ваш код считывает весь входной файл как одну строку, а ваш комментарий («отличное решение») к другому ответу подразумевает, что вам наплевать на символы новой строки в ваших данных. Если ваш файл содержит две такие строки:

this is line 1
this is line 2

результат будет 'this is line 1this is line 2'... это то, чего вы действительно хотите?

Лучшее решение будет включать:

  1. лучшее имя для функции фильтра, чем onlyascii
  2. признание того, что функция фильтра просто должна возвращать истинное значение, если аргумент должен быть сохранен:

    def filter_func(char):
        return char == '\n' or 32 <= ord(char) <= 126
    # and later:
    filtered_data = filter(filter_func, data).lower()
Джон Мачин
источник
Этот ответ очень полезен для тех из нас, кто хочет задать что-то похожее на OP, и ваш предлагаемый ответ полезен на питоническом языке. Однако мне кажется странным, что нет более эффективного решения проблемы, как вы ее интерпретировали (с чем я часто сталкиваюсь) - посимвольно, это занимает очень много времени в очень большом файле.
Xodarap777
5

Вы можете использовать следующий код для удаления неанглийских букв:

import re
str = "123456790 ABC#%? .(朱惠英)"
result = re.sub(r'[^\x00-\x7f]',r'', str)
print(result)

Это вернет

123456790 ABC #%? . ()

Ноха Эльпринс
источник
1

Если вам нужны печатные символы ascii, вам, вероятно, следует исправить свой код следующим образом:

if ord(char) < 32 or ord(char) > 126: return ''

это эквивалентно string.printable(ответ от @jterrace), за исключением отсутствия возвратов и вкладок ('\ t', '\ n', '\ x0b', '\ x0c' и '\ r'), но не соответствует диапазон по вашему вопросу

Хоакин
источник
1
Чуть проще: lambda x: 32 <= ord (x) <= 126
jterrace
это не то же самое, что string.printable, потому что оно не учитывает string.whitespace, хотя это может быть то, что хочет OP, зависит от таких вещей, как \ n и \ t.
jterrace
@jterrace right, включает пробел (ord 32), но без возвратов и вкладок
Хоакин
да, просто комментирую «это эквивалентно string.printable», но это неправда
jterrace
Отредактировал ответ, спасибо! вопрос OP вводит в заблуждение, если вы не читаете его внимательно.
Хоакин
1

Работаю на Fluent Python (Ramalho) - настоятельно рекомендую. Составьте список однострочников для понимания, вдохновленных главой 2:

onlyascii = ''.join([s for s in data if ord(s) < 127])
onlymatch = ''.join([s for s in data if s in
              'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'])
Мэтью Данн
источник
Это не позволит использовать стандартные символы ASCII, такие как маркеры, символ градусов, символ авторского права, символ йены и т. Д. Кроме того, ваш первый пример включает непечатаемые символы, такие как BELL, что нежелательно.
Шерил Хохман