Як я можу видалити символи, що не належать до ASCII, але залишити періоди та пробіли за допомогою Python?


100

Я працюю з файлом .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 (), щоб залишити пробіли та періоди? Я думаю, що це не надто складно, але я не можу цього зрозуміти.


Дякую (щиро) за роз'яснення Джон. Я зрозумів, що пробіли та періоди - це символи ASCII. Однак я видалив їх обох ненавмисно, намагаючись видалити лише символи, що не належать до ASCII. Я бачу, як моє запитання могло означати інакше.

@PoliticalEconomist: Ваша проблема ще недостатньо визначена. Дивіться мою відповідь.
Джон Махін

Відповіді:


187

Ви можете відфільтрувати всі символи з рядка, який не можна друкувати , використовуючи 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

EDIT: На Python 3 фільтр поверне ітерабельний номер. Правильний спосіб повернути рядок назад:

''.join(filter(lambda x: x in printable, s))

2
що з тими символами для друку, які знаходяться нижче порядкових 48?
Хоакін

38
Єдина проблема використання filter- це те, що він повертає ітерабельний номер. Якщо вам потрібна тятива (як я зробив , тому що мені потрібно було це робити , коли стиснення списку) , то зробити це: ''.join(filter(lambda x: x in string.printable, s).
cjbarth

5
@cjbarth - коментар python 3 специфічний, але дуже корисний. Дякую!
підкреслення

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: str -> bytes -> str

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

Python2: unicode -> str -> unicode

>>> 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'

16
Я отримуюUnicodeDecodeError: 'ascii' codec can't decode byte 0xc2 in position 27
Xodarap777

2
Я отримав цю помилку, коли я вставив фактичний символ Unicode в рядок за допомогою копіювальної пасти. Якщо ви вказали рядок як u'thestring ', кодер працює правильно.
Бен Ліянаж

2
Працює лише на Py3, але це елегантно.
габоровий

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
Це рішення відповідає на заявлене запитання ОП, але майте на увазі, що воно не видалить символи, що не друкуються, включені до ASCII, які, на мою думку, мають намір задати ОП.
Даніло Суза Мораш

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()

Ця відповідь дуже корисна тим, хто з нас заходить, щоб запитати щось подібне до ОП, і запропонована Вами відповідь є корисно піфонічною. Однак мені здається дивним, що немає більш ефективного рішення проблеми, як ви її інтерпретували (що я часто стикаюся) - характер за характером, це займає дуже великий час у дуже великому файлі.
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
Трохи простіше: лямбда x: 32 <= ord (x) <= 126
jterrace

це не те саме, що string.printable, оскільки він залишає string.whitespace, хоча це може бути те, чого хоче ОП, залежить від речей, таких як \ n та \ t.
jterrace

@jterrace праворуч, містить пробіл (порядк 32), але немає повернень та вкладок
joaquin

так, просто коментуючи "це еквівалентно string.printable", але не відповідає дійсності
jterrace

Я відредагував відповідь, дякую! питання ОП є оманливим, якщо ви не прочитаєте його уважно.
Жоакін

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, що небажано.
SherylHohman
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.