Wie kann ich Nicht-ASCII-Zeichen entfernen, aber mit Python Punkte und Leerzeichen lassen?

100

Ich arbeite mit einer TXT-Datei. Ich möchte eine Zeichenfolge des Textes aus der Datei ohne Nicht-ASCII-Zeichen. Ich möchte jedoch Leerzeichen und Punkte lassen. Zur Zeit ziehe ich diese auch aus. Hier ist der Code:

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

Wie soll ich onlyascii () ändern, um Leerzeichen und Punkte zu lassen? Ich stelle mir vor, es ist nicht zu kompliziert, aber ich kann es nicht herausfinden.

alexwlchan
quelle
Vielen Dank (aufrichtig) für die Klarstellung John. Ich habe verstanden, dass Leerzeichen und Punkte ASCII-Zeichen sind. Ich habe jedoch beide unbeabsichtigt entfernt, während ich versucht habe, nur Nicht-ASCII-Zeichen zu entfernen. Ich sehe, wie meine Frage etwas anderes impliziert haben könnte.
@PoliticalEconomist: Ihr Problem ist immer noch sehr unterbestimmt. Siehe meine Antwort.
John Machin

Antworten:

185

Sie können alle Zeichen aus der Zeichenfolge, die nicht gedruckt werden können , mit string.printable wie folgt filtern :

>>> 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 auf meinem Computer enthält:

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

BEARBEITEN: Unter Python 3 gibt der Filter eine iterable zurück. Der richtige Weg, um einen String zurück zu bekommen, wäre:

''.join(filter(lambda x: x in printable, s))
jterrace
quelle
2
Was ist mit diesen druckbaren Zeichen los, die unter der Ordnungszahl 48 liegen?
Joaquin
38
Das einzige Problem bei der Verwendung filterist, dass eine iterable zurückgegeben wird. Wenn Sie eine Zeichenfolge zurück benötigen (wie ich es getan habe, weil ich diese bei der Listenkomprimierung benötigt habe), gehen Sie folgendermaßen vor : ''.join(filter(lambda x: x in string.printable, s).
CJBarth
5
@cjbarth - Kommentar ist Python 3-spezifisch, aber sehr nützlich. Vielen Dank!
Unterschock
7
Warum nicht den regulären Ausdruck verwenden : re.sub(r'[^\x00-\x7f]',r'', your-non-ascii-string). Siehe diesen Thread stackoverflow.com/a/20079244/658497
Noam Manos
1
@ NoamManos das war 4-5 mal schneller für mich als der join ... filter ... lambda lösung, danke.
Artfulrobot
95

Eine einfache Möglichkeit, zu einem anderen Codec zu wechseln, ist die Verwendung von encode () oder decode (). In Ihrem Fall möchten Sie in ASCII konvertieren und alle Symbole ignorieren, die nicht unterstützt werden. Zum Beispiel ist der schwedische Buchstabe å kein ASCII-Zeichen:

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

Bearbeiten:

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 (dekodieren und kodieren in umgekehrter Reihenfolge)

>>> "hej d\xe5".decode("ascii", errors="ignore").encode()
'hej d'
Zweedeend
quelle
16
Ich bekommeUnicodeDecodeError: 'ascii' codec can't decode byte 0xc2 in position 27
Xodarap777
2
Ich habe diesen Fehler erhalten, als ich das eigentliche Unicode-Zeichen per Copy Paste in die Zeichenfolge eingefügt habe. Wenn Sie eine Zeichenfolge als u'thestring 'angeben, funktioniert die Codierung ordnungsgemäß.
Ben Liyanage
2
Funktioniert nur mit Py3, ist aber elegant.
gaborous
7
Für diejenigen, die den gleichen Fehler wie @ Xodarap777 erhalten: Sie sollten zuerst die Zeichenfolge .decode () und erst danach codieren. Zum Beispiels.decode('utf-8').encode('ascii', errors='ignore')
Spc_555
30

Laut @artfulrobot sollte dies schneller sein als Filter und Lambda:

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

Weitere Beispiele finden Sie hier http://stackoverflow.com/questions/20078816/replace-non-ascii-characters-with-a-single-space/20079244#20079244

Noam Manos
quelle
1
Diese Lösung beantwortet die gestellte Frage von OP, aber achten Sie darauf, dass nicht druckbare Zeichen, die in ASCII enthalten sind, nicht entfernt werden, was OP meiner Meinung nach beabsichtigt hat.
Danilo Souza Morães
6

Ihre Frage ist nicht eindeutig; Die ersten beiden Sätze zusammen bedeuten, dass Sie glauben, dass Leerzeichen und "Punkt" keine ASCII-Zeichen sind. Das ist falsch. Alle Zeichen, so dass ord (char) <= 127 ist, sind ASCII-Zeichen. Zum Beispiel schließt Ihre Funktion diese Zeichen aus! "# $% & \ '() * +, -. / Enthält jedoch mehrere andere, z. B. [] {}.

Bitte treten Sie zurück, überlegen Sie ein wenig und bearbeiten Sie Ihre Frage, um uns mitzuteilen, was Sie versuchen, ohne das Wort ASCII zu erwähnen, und warum Sie der Meinung sind, dass Zeichen wie ord (char)> = 128 ignorierbar sind. Auch: welche Version von Python? Wie ist die Kodierung Ihrer Eingabedaten?

Bitte beachten Sie, dass Ihr Code die gesamte Eingabedatei als einzelne Zeichenfolge liest und Ihr Kommentar ("großartige Lösung") zu einer anderen Antwort impliziert, dass Sie sich nicht für Zeilenumbrüche in Ihren Daten interessieren. Wenn Ihre Datei zwei Zeilen wie diese enthält:

this is line 1
this is line 2

das Ergebnis wäre 'this is line 1this is line 2' ... ist es das, was Sie wirklich wollen?

Eine bessere Lösung wäre:

  1. ein besserer Name für die Filterfunktion als onlyascii
  2. Erkennen, dass eine Filterfunktion lediglich einen Wahrheitswert zurückgeben muss, wenn das Argument beibehalten werden soll:

    def filter_func(char):
        return char == '\n' or 32 <= ord(char) <= 126
    # and later:
    filtered_data = filter(filter_func, data).lower()
John Machin
quelle
Diese Antwort ist sehr hilfreich für diejenigen von uns, die hereinkommen, um etwas Ähnliches wie das OP zu fragen, und Ihre vorgeschlagene Antwort ist hilfreich pythonisch. Ich finde es jedoch seltsam, dass es keine effizientere Lösung für das Problem gibt, wie Sie es interpretiert haben (was mir oft begegnet) - Zeichen für Zeichen dauert dies in einer sehr großen Datei sehr lange.
Xodarap777
5

Sie können den folgenden Code verwenden, um nicht englische Buchstaben zu entfernen:

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

Dies wird zurückkehren

123456790 ABC #%? ()

Noha Elprince
quelle
1

Wenn Sie druckbare ASCII-Zeichen möchten, sollten Sie Ihren Code wahrscheinlich wie folgt korrigieren:

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

Dies entspricht string.printable(Antwort von @jterrace), mit Ausnahme des Fehlens von Rückgaben und Tabulatoren ('\ t', '\ n', '\ x0b', '\ x0c' und '\ r'), entspricht jedoch nicht die Reichweite auf Ihre Frage

Joaquin
quelle
1
Etwas einfacher: Lambda x: 32 <= ord (x) <= 126
jterrace
Dies ist nicht dasselbe wie string.printable, da string.whitespace weggelassen wird, obwohl dies möglicherweise vom OP gewünscht wird, hängt es von Dingen wie \ n und \ t ab.
Jterrace
@jterrace rechts, enthält Leerzeichen (ord 32), aber keine Rückgabe und Tabulatoren
Joaquin
Ja, ich kommentiere nur "das ist gleichbedeutend mit string.printable", aber nicht wahr
jterrace
Ich habe die Antwort bearbeitet, danke! Die OP-Frage ist irreführend, wenn Sie sie nicht sorgfältig lesen.
Joaquin
1

Ich arbeite mich durch Fluent Python (Ramalho) - sehr zu empfehlen. Listenverständnis One-Ish-Liner, inspiriert von Kapitel 2:

onlyascii = ''.join([s for s in data if ord(s) < 127])
onlymatch = ''.join([s for s in data if s in
              'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'])
Matthew Dunn
quelle
Dies würde keine Standard-ASCII-Symbole wie Aufzählungszeichen, Gradsymbol, Copyright-Symbol, Yen-Symbol usw. zulassen. Außerdem enthält Ihr erstes Beispiel nicht druckbare Symbole wie BELL, was unerwünscht ist.
SherylHohman