Удаление непечатаемых символов из строки в Python


91

Я использую для бега

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

на Perl, чтобы избавиться от непечатаемых символов.

В Python нет классов регулярных выражений POSIX, и я не могу написать [: print:], чтобы он означал то, что я хочу. Я не знаю, как в Python определить, можно ли распечатать символ.

Что бы вы сделали?

EDIT: он также должен поддерживать символы Unicode. Метод string.printable удалит их из вывода. curses.ascii.isprint вернет false для любого символа Юникода.

Ответы:


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"? Я не знаю, я просто спрашиваю - мне кажется, что некоторые из других категорий «C» также могут быть кандидатами на этот фильтр.
Патрик Джонмейер,

1
Эта функция, как опубликовано, удаляет половину еврейских символов. Я получаю одинаковый эффект для обоих приведенных методов.
dotancohen

1
С точки зрения производительности, разве string.translate () в этом случае не будет работать быстрее? См. Stackoverflow.com/questions/265960/…
Кашьяп

3
Используйте, all_chars = (unichr(i) for i in xrange(sys.maxunicode))чтобы избежать ошибки узкой сборки.
danmichaelo

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

74

Насколько я знаю, наиболее питоническим / эффективным методом будет:

import string

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

10
Вы, вероятно, захотите filter_string = '' .join (filter (lambda x: x in string.printable, myStr), чтобы вы вернули строку.
Натан Шивели-Сандерс,

12
К сожалению, string.printable не содержит символов Юникода, и поэтому ü или ó не будет на выходе ... может быть, есть что-то еще?
Винко Врсалович

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

3
@AaronGallagher: на 99,9% быстрее? Откуда вы берете эту фигуру? Сравнение производительности далеко не так плохо.
Крис Морган

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 Все три действительны и дают один и тот же набор. Возможно, ваш лучший способ указать литерал набора.
Бер

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.


Это единственный ответ, который у меня работает с символами Юникода. Здорово, что вы предоставили тестовые примеры!
пир

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 линии. спасибо
Chop Labalagun

1
по какой-то причине это отлично работает в Windows, но не может использовать его в Linux, мне пришлось изменить f на r, но я не уверен, что это решение.
Chop Labalagun

Похоже, ваш Linux Python был слишком стар, чтобы поддерживать f-строки. Р-струны совсем другие, можно сказать r'[^' + re.escape(string.printable) + r']'. (Я не думаю, что re.escape()здесь полностью правильно, но если это
сработает

2

Лучшее, что я придумал, это (спасибо python-izers выше)

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"
Героид Мерфи

7
Но есть символы Unicode, которые тоже нельзя распечатать.
Tripleee

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 и многие другие. Использование (сигнатуры методов) очень похоже на использование Python re.

Из документации:

[[: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))

Собственно и квадратные скобки вам не нужны.
tripleee

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.