Entfernen nicht druckbarer Zeichen aus einer Zeichenfolge in Python


88

Ich renne

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

auf Perl, um nicht druckbare Zeichen loszuwerden.

In Python gibt es keine POSIX-Regex-Klassen, und ich kann nicht schreiben [: print:], damit es bedeutet, was ich will. Ich kenne in Python keine Möglichkeit zu erkennen, ob ein Zeichen druckbar ist oder nicht.

Was würden Sie tun?

EDIT: Es muss auch Unicode-Zeichen unterstützen. Der string.printable-Weg entfernt sie gerne aus der Ausgabe. curses.ascii.isprint gibt für jedes Unicode-Zeichen false zurück.

Antworten:


82

Das Iterieren über Strings ist in Python leider eher langsam. Reguläre Ausdrücke sind für solche Dinge um eine Größenordnung schneller. Sie müssen nur die Charakterklasse selbst erstellen. Das Unicodedata- Modul ist hierfür sehr hilfreich, insbesondere die Funktion unicodedata.category () . Beschreibungen der Kategorien finden Sie unter Unicode-Zeichendatenbank .

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)

Für 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)

In einigen Anwendungsfällen sind möglicherweise zusätzliche Kategorien (z. B. alle aus der Kontrollgruppe) vorzuziehen, obwohl dies die Verarbeitungszeit verlangsamen und die Speichernutzung erheblich erhöhen kann. Anzahl der Zeichen pro Kategorie:

  • Cc (Kontrolle): 65
  • Cf (Format): 161
  • Cs (Ersatz): 2048
  • Co (für den privaten Gebrauch): 137468
  • Cn (nicht zugewiesen): 836601

Bearbeiten Hinzufügen von Vorschlägen aus den Kommentaren.


4
Ist 'Cc' hier genug? Ich weiß nicht, ich frage nur - es scheint mir, dass einige der anderen 'C'-Kategorien auch Kandidaten für diesen Filter sind.
Patrick Johnmeyer

1
Diese Funktion entfernt, wie veröffentlicht, die Hälfte der hebräischen Zeichen. Ich bekomme für beide Methoden den gleichen Effekt.
Dotancohen

1
Würde string.translate () in diesem Fall aus Sicht der Leistung nicht schneller funktionieren? Siehe stackoverflow.com/questions/265960/…
Kashyap

3
Verwenden Sie all_chars = (unichr(i) for i in xrange(sys.maxunicode))diese Option , um den engen Erstellungsfehler zu vermeiden.
Danmichaelo

4
Für mich control_chars == '\x00-\x1f\x7f-\x9f'(getestet auf Python 3.5.2)
AXO

72

Soweit ich weiß, wäre die pythonischste / effizienteste Methode:

import string

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

10
Sie möchten wahrscheinlich filtered_string = '' .join (Filter (Lambda x: x in string.printable, myStr), damit Sie einen String zurückerhalten.
Nathan Shively-Sanders

12
Leider enthält string.printable keine Unicode-Zeichen und daher wird ü oder ó nicht in der Ausgabe enthalten sein ... Vielleicht gibt es noch etwas anderes?
Vinko Vrsalovic

17
Sie sollten ein Listenverständnis oder Generatorausdrücke verwenden, nicht Filter + Lambda. Eine davon wird in 99,9% der Fälle schneller sein. '' .join (s für s in myStr wenn s in string.printable)
habnabit

3
@ AaronGallagher: 99,9% schneller? Woher pflücken Sie diese Figur? Der Leistungsvergleich ist bei weitem nicht so schlecht.
Chris Morgan

4
Hallo William. Diese Methode scheint alle Nicht-ASCII-Zeichen zu entfernen. Es gibt viele druckbare Nicht-ASCII-Zeichen in Unicode!
Dotancohen

17

Sie können versuchen, einen Filter mit der folgenden unicodedata.category()Funktion einzurichten :

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

Siehe Tabelle 4-9 auf Seite 175 in den Eigenschaften der Unicode-Datenbankzeichen für die verfügbaren Kategorien


Sie haben ein Listenverständnis gestartet, das nicht in Ihrer letzten Zeile endete. Ich schlage vor, Sie entfernen die Öffnungshalterung vollständig.
Zot

Vielen Dank für den Hinweis. Ich habe den Beitrag entsprechend bearbeitet
Ber

1
Dies scheint die direkteste und einfachste Methode zu sein. Vielen Dank.
Dotancohen

1
@CsabaToth Alle drei sind gültig und ergeben den gleichen Satz. Ihre ist vielleicht die schönste Art, ein festgelegtes Literal anzugeben.
Ber

1
@AnubhavJhalani Sie können dem Filter weitere Unicode-Kategorien hinzufügen. Um neben Buchstaben auch Leerzeichen und Ziffern zu reservieren, verwenden Sieprintable = {'Lu', 'Ll', Zs', 'Nd'}
Ber

10

In 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})

In diesem StackOverflow-Beitrag zum Entfernen von Interpunktion erfahren Sie, wie .translate () mit Regex & .replace () verglichen wird.

Die Bereiche können nonprintable = (ord(c) for c in (chr(i) for i in range(sys.maxunicode)) if unicodedata.category(c)=='Cc')mithilfe der von @Ants Aasma gezeigten Unicode-Zeichendatenbankkategorien generiert werden .


Es wäre besser, Unicode-Bereiche zu verwenden (siehe Antwort von @Ants Aasma). Das Ergebnis wäre text.translate({c:None for c in itertools.chain(range(0x00,0x20),range(0x7f,0xa0))}).
Darkdragon

8

Das Folgende funktioniert mit Unicode-Eingaben und ist ziemlich schnell ...

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

Meine eigenen Tests legen nahe, dass dieser Ansatz schneller ist als Funktionen, die über die Zeichenfolge iterieren und mit ein Ergebnis zurückgeben str.join.


Dies ist die einzige Antwort, die für mich mit Unicode-Zeichen funktioniert. Super, dass Sie Testfälle zur Verfügung gestellt haben!
Pir

1
Wenn Sie Zeilenumbrüche zulassen möchten, fügen Sie LINE_BREAK_CHARACTERS = set(["\n", "\r"])und and not chr(i) in LINE_BREAK_CHARACTERSbeim Erstellen der Tabelle hinzu.
Pir

5

Diese Funktion verwendet Listenverständnis und str.join, sodass sie in linearer Zeit anstelle von O (n ^ 2) ausgeführt wird:

from curses.ascii import isprint

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

2
filter(isprint,input)
Yingted

5

Noch eine Option in Python 3:

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

Das hat super gut funktioniert für mich und seine 1 Linie. danke
Chop Labalagun

1
Aus irgendeinem Grund funktioniert dies unter Windows hervorragend, kann es aber unter Linux nicht verwenden. Ich musste das f für ein r ändern, bin mir aber nicht sicher, ob dies die Lösung ist.
Chop Labalagun

Klingt so, als wäre Ihr Linux-Python zu alt, um damals F-Strings zu unterstützen. R-Strings sind ganz anders, obwohl man sagen könnte r'[^' + re.escape(string.printable) + r']'. (Ich denke nicht, dass re.escape()es hier ganz richtig ist, aber wenn es funktioniert ...)
Tripleee

2

Das Beste, was ich mir jetzt ausgedacht habe, ist (dank der Python-Izer oben)

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

Nur so habe ich herausgefunden, dass es mit Unicode-Zeichen / Zeichenfolgen funktioniert

Irgendwelche besseren Optionen?


1
Sofern Sie nicht mit Python 2.3 arbeiten, sind die inneren [] redundant. "return '' .join (c für c ...)"
habnabit

Nicht ganz redundant - sie haben unterschiedliche Bedeutungen (und Leistungsmerkmale), obwohl das Endergebnis das gleiche ist.
Meilen

Sollte das andere Ende des Bereichs nicht auch geschützt werden?: "Ord (c) <= 126"
Gearoid Murphy

7
Es gibt jedoch auch Unicode-Zeichen, die nicht gedruckt werden können.
Tripleee

2

Der eine unten ist schneller als der andere oben. Schau mal

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

In Python gibt es keine POSIX-Regex-Klassen

Es gibt bei der Verwendung der regexBibliothek: https://pypi.org/project/regex/

Es ist gut gepflegt und unterstützt Unicode Regex, Posix Regex und viele mehr. Die Verwendung (Methodensignaturen) ist der von Python sehr ähnlich re.

Aus der Dokumentation:

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

POSIX-Zeichenklassen werden unterstützt. Diese werden normalerweise als alternative Form von behandelt \p{...}.

(Ich bin nicht verbunden, nur ein Benutzer.)


1

Basierend auf der Antwort von @ Ber empfehle ich, nur Steuerzeichen zu entfernen, wie in den Kategorien der Unicode-Zeichendatenbank definiert :

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

Das ist eine großartige Antwort!
tdc

0

Um 'Leerzeichen' zu entfernen,

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

Eigentlich brauchen Sie dann auch keine eckigen Klammern.
Tripleee

0

Nach Antworten von Ants Aasma und Shawnrad :

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)

getestet auf Python 3.7.7

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.