Видалення недрукованих символів із рядка в python


91

Я звик бігати

$s =~ s/[^[:print:]]//g;

на Perl, щоб позбутися недрукованих символів.

У Python немає класів регулярних виразів POSIX, і я не можу писати [: print:], якщо це означає те, що я хочу. Я не знаю жодного способу в Python виявити, чи можна друкувати символ.

Що б ти зробив?

EDIT: Він також повинен підтримувати символи Unicode. Шлях string.printable щасливо позбавить їх вихідних даних. curses.ascii.isprint поверне значення false для будь-якого символу Unicode.

Відповіді:


85

На жаль, ітерація рядків у Python відбувається досить повільно. Регулярні вирази для цього типу на порядок швидші. Вам просто потрібно побудувати клас персонажів самостійно. Для цього дуже корисний модуль unicodedata , особливо функція unicodedata.category () . Див бази даних символів Unicode для опису категорій.

import unicodedata, re, itertools, sys

all_chars = (chr(i) for i in range(sys.maxunicode))
categories = {'Cc'}
control_chars = ''.join(c for c in all_chars if unicodedata.category(c) in categories)
# or equivalently and much more efficiently
control_chars = ''.join(map(chr, itertools.chain(range(0x00,0x20), range(0x7f,0xa0))))

control_char_re = re.compile('[%s]' % re.escape(control_chars))

def remove_control_chars(s):
    return control_char_re.sub('', s)

Для Python2

import unicodedata, re, sys

all_chars = (unichr(i) for i in xrange(sys.maxunicode))
categories = {'Cc'}
control_chars = ''.join(c for c in all_chars if unicodedata.category(c) in categories)
# or equivalently and much more efficiently
control_chars = ''.join(map(unichr, range(0x00,0x20) + range(0x7f,0xa0)))

control_char_re = re.compile('[%s]' % re.escape(control_chars))

def remove_control_chars(s):
    return control_char_re.sub('', s)

У деяких випадках використання додаткові категорії (наприклад, усі з контрольної групи можуть бути кращими, хоча це може сповільнити час обробки та значно збільшити використання пам'яті. Кількість символів у категорії:

  • Cc (контроль): 65
  • Cf (формат): 161
  • Cs (сурогат): 2048
  • Co (приватне використання): 137468
  • Cn (без призначення): 836601

Редагувати Додавання пропозицій із коментарів.


4
Чи достатньо тут 'Cc'? Не знаю, я просто запитую - мені здається, що деякі інші категорії "С" також можуть бути кандидатами на цей фільтр.
Патрік Джонмайєр,

1
Ця функція, опублікована, видаляє половину івритських символів. Я отримую однаковий ефект для обох наведених методів.
dotancohen

1
З точки зору продуктивності, чи не буде string.translate () працювати швидше в цьому випадку? Дивіться stackoverflow.com/questions/265960/…
Kashyap

3
Використовуйте, all_chars = (unichr(i) for i in xrange(sys.maxunicode))щоб уникнути вузької помилки збірки.
danmichaelo

4
Для мене control_chars == '\x00-\x1f\x7f-\x9f'(протестовано на Python 3.5.2)
AXO

73

Наскільки мені відомо, найбільш пітонічним / ефективним методом буде:

import string

filtered_string = filter(lambda x: x in string.printable, myStr)

10
Ви, напевно, хочете filtered_string = '' .join (фільтр (лямбда x: x у string.printable, myStr), щоб ви повернули рядок.
Nathan Shively-Sanders

12
На жаль, string.printable не містить символів Unicode, і, отже, ü або ó не буде у вихідних даних ... можливо, є щось інше?
Вінко Врсалович

17
Ви повинні використовувати розуміння списку або вирази генератора, а не фільтр + лямбда. Один із них у 99,9% випадків буде швидшим. '' .join (s для s в myStr, якщо s в string.printable)
habnabit

3
@AaronGallagher: на 99,9% швидше? Звідки ти вириваєш цю фігуру? Порівняння продуктивності не так вже й погане.
Chris Morgan

4
Привіт Вільяме. Здається, цей метод видаляє всі символи, що не належать до ASCII. У Unicode багато символів для друку, які не належать до ASCII!
dotancohen

17

Ви можете спробувати налаштувати фільтр за допомогою unicodedata.category()функції:

import unicodedata
printable = {'Lu', 'Ll'}
def filter_non_printable(str):
  return ''.join(c for c in str if unicodedata.category(c) in printable)

Див. Таблицю 4-9 на сторінці 175 у властивостях символів бази даних Unicode щодо доступних категорій


ви розпочали розуміння списку, який не закінчився у вашому останньому рядку. Я пропоную вам повністю зняти кронштейн, що відкривається.
tzot

Дякуємо, що вказали на це. Я відповідно відредагував допис
Бер,

1
Це здається найбільш прямим, прямим методом. Дякую.
dotancohen

1
@CsabaToth Усі три дійсні і дають однаковий набір. Your - це, мабуть, найкращий спосіб вказати набір літералів.
Бер

1
@AnubhavJhalani Ви можете додати до фільтру більше категорій Unicode. Щоб зарезервувати пробіли та цифри на додаток до букв, використовуйтеprintable = {'Lu', 'Ll', Zs', 'Nd'}
Бер

11

У Python 3,

def filter_nonprintable(text):
    import itertools
    # Use characters of control category
    nonprintable = itertools.chain(range(0x00,0x20),range(0x7f,0xa0))
    # Use translate to remove all non-printable characters
    return text.translate({character:None for character in nonprintable})

Дивіться цей допис StackOverflow про видалення знаків пунктуації щодо порівняння .translate () із регулярним виразом & .replace ()

Діапазони можуть бути сформовані за nonprintable = (ord(c) for c in (chr(i) for i in range(sys.maxunicode)) if unicodedata.category(c)=='Cc')допомогою категорій баз даних символів Unicode, як показано @Ants Aasma.


Було б краще використовувати діапазони Unicode (див. Відповідь @Ants Aasma). Результат буде text.translate({c:None for c in itertools.chain(range(0x00,0x20),range(0x7f,0xa0))}).
darkdragon

9

Далі буде працювати з введенням Unicode і досить швидко ...

import sys

# build a table mapping all non-printable characters to None
NOPRINT_TRANS_TABLE = {
    i: None for i in range(0, sys.maxunicode + 1) if not chr(i).isprintable()
}

def make_printable(s):
    """Replace non-printable characters in a string."""

    # the translate method on str removes characters
    # that map to None from the string
    return s.translate(NOPRINT_TRANS_TABLE)


assert make_printable('Café') == 'Café'
assert make_printable('\x00\x11Hello') == 'Hello'
assert make_printable('') == ''

Моє власне тестування припускає, що такий підхід швидший, ніж функції, які перебирають рядок і повертають результат за допомогою str.join.


Це єдина відповідь, яка працює для мене з символами Unicode. Чудово, що ви надали тестові кейси!
пір

1
Якщо ви хочете дозволити розриви рядків, додайте LINE_BREAK_CHARACTERS = set(["\n", "\r"])і and not chr(i) in LINE_BREAK_CHARACTERSпри побудові таблиці.
пір

5

Ця функція використовує розуміння списку та str.join, тому працює в лінійний час замість O (n ^ 2):

from curses.ascii import isprint

def printable(input):
    return ''.join(char for char in input if isprint(char))

2
filter(isprint,input)
yingted

5

Ще один варіант у python 3:

re.sub(f'[^{re.escape(string.printable)}]', '', my_string)

Це спрацювало надзвичайно добре для мене і його 1 рядок. подяка
Чоп Лабалагун

1
чомусь це чудово працює на Windows, але не можу використовувати його на Linux, мені довелося змінити f на r, але я не впевнений, що це рішення.
Чоп Лабалагун

Здається, тоді ваш Linux Python був занадто старим, щоб підтримувати f-рядки. r-рядки досить різні, хоча можна сказати r'[^' + re.escape(string.printable) + r']'. (Я не думаюre.escape() тут не зовсім правильно, але якщо це спрацює ...)
триплі

2

Найкраще, що я придумав зараз, - це (завдяки пітон-Айзерам вище)

def filter_non_printable(str):
  return ''.join([c for c in str if ord(c) > 31 or ord(c) == 9])

Це єдиний спосіб, який я дізнався, що працює з символами / рядками Unicode

Є якісь кращі варіанти?


1
Якщо ви не користуєтесь python 2.3, внутрішні [] є зайвими. "return '' .join (c for c ...)"
habnabit

Не зовсім зайві - вони мають різне значення (і експлуатаційні характеристики), хоча кінцевий результат однаковий.
Майлз

Чи не повинен бути захищений і інший кінець діапазону ?: "ord (c) <= 126"
Gearoid Murphy

7
Але є символи Unicode, які також не підлягають друку.
триплеє

2

Той, що нижче, працює швидше, ніж інші вище. Поглянь

''.join([x if x in string.printable else '' for x in Str])

"".join([c if 0x21<=ord(c) and ord(c)<=0x7e else "" for c in ss])
evandrix

2

У Python немає класів регулярних виразів POSIX

Є при використанні regex бібліотеки є: https://pypi.org/project/regex/

Він добре підтримується і підтримує регулярний вираз Unicode, регулярний вираз Posix та багато інших. Використання (підписи методів) є дуже схоже на використання Pythonre .

З документації:

[[:alpha:]]; [[:^alpha:]]

Підтримуються класи символів POSIX. Вони зазвичай розглядаються як альтернативна форма\p{...} .

(Я не афілійований, лише користувач.)


2

На основі відповіді @ Ber пропоную видалити лише контрольні символи, визначені в категоріях баз даних символів Unicode :

import unicodedata
def filter_non_printable(s):
    return ''.join(c for c in s if not unicodedata.category(c).startswith('C'))

Це чудова відповідь!
tdc

Можливо, ви щось задумали, startswith('C')але це було набагато менш ефективним під час мого тестування, ніж будь-яке інше рішення.
Big McLargeHuge

big-mclargehuge: Метою мого рішення було поєднання повноти та простоти / читабельності. Ви можете спробувати використовувати if unicodedata.category(c)[0] != 'C'замість цього. Чи працює він краще? Якщо ви віддаєте перевагу швидкість виконання над вимогами до пам'яті, можна заздалегідь обчислити таблицю , як показано в stackoverflow.com/a/93029/3779655
darkdragon

0

Щоб видалити "пробіли",

import re
t = """
\n\t<p>&nbsp;</p>\n\t<p>&nbsp;</p>\n\t<p>&nbsp;</p>\n\t<p>&nbsp;</p>\n\t<p>
"""
pat = re.compile(r'[\t\n]')
print(pat.sub("", t))

Насправді вам тоді не потрібні квадратні дужки.
триплеє

0

Адаптовано з відповідей Антса Асми та Шонрад :

nonprintable = set(map(chr, list(range(0,32)) + list(range(127,160))))
ord_dict = {ord(character):None for character in nonprintable}
def filter_nonprintable(text):
    return text.translate(ord_dict)

#use
str = "this is my string"
str = filter_nonprintable(str)
print(str)

протестовано на Python 3.7.7

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