Jak mogę usunąć znaki spoza zestawu ASCII, ale pozostawić kropki i spacje w Pythonie?

101

Pracuję z plikiem .txt. Chcę ciąg tekstu z pliku bez znaków spoza zestawu ASCII. Chcę jednak zostawić spacje i kropki. W tej chwili też je rozdzieram. Oto kod:

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

Jak zmodyfikować onlyascii (), aby pozostawić spacje i kropki? Wyobrażam sobie, że nie jest to zbyt skomplikowane, ale nie mogę tego rozgryźć.

alexwlchan
źródło
Dzięki (szczerze) za wyjaśnienie John. Zrozumiałem, że spacje i kropki to znaki ASCII. Jednak nieumyślnie usuwałem oba z nich, próbując usunąć tylko znaki spoza ASCII. Widzę, jak moje pytanie mogłoby sugerować inaczej.
@PoliticalEconomist: Twój problem jest nadal bardzo niedookreślony. Zobacz moją odpowiedź.
John Machin

Odpowiedzi:

188

Możesz odfiltrować wszystkie znaki z ciągu, których nie można wydrukować, używając string.printable , na przykład:

>>> 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 na moim komputerze zawiera:

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

EDYCJA: W Pythonie 3 filtr zwróci iterowalny. Prawidłowy sposób na odzyskanie łańcucha to:

''.join(filter(lambda x: x in printable, s))
jterrace
źródło
2
co jest z tymi drukowalnymi znakami, które są poniżej liczby porządkowej 48?
joaquin
38
Jedynym problemem związanym z użyciem filterjest to, że zwraca iterowalną. Jeśli potrzebujesz plecy string (jak ja, bo to potrzebne podczas wykonywania kompresji listy), a następnie to zrobić: ''.join(filter(lambda x: x in string.printable, s).
cjbarth,
5
@cjbarth - komentarz jest specyficzny dla Pythona 3, ale bardzo przydatny. Dzięki!
undershock
7
Dlaczego nie użyć wyrażenia regularnego: re.sub(r'[^\x00-\x7f]',r'', your-non-ascii-string). Zobacz ten wątek stackoverflow.com/a/20079244/658497
Noam Manos
1
@NoamManos to było dla mnie 4-5 razy szybsze niż połączenie ... filtru ... rozwiązanie lambda, dzięki.
artfulrobot
95

Łatwym sposobem zmiany na inny kodek jest użycie metody encode () lub decode (). W twoim przypadku chcesz przekonwertować na ASCII i zignorować wszystkie symbole, które nie są obsługiwane. Na przykład szwedzka litera å nie jest znakiem 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

Edytować:

Python3: str -> bajty -> 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 (dekoduj i koduj w odwrotnej kolejności)

>>> "hej d\xe5".decode("ascii", errors="ignore").encode()
'hej d'
Zweedeend
źródło
16
DostajęUnicodeDecodeError: 'ascii' codec can't decode byte 0xc2 in position 27
Xodarap777
2
Otrzymałem ten błąd, kiedy umieściłem rzeczywisty znak Unicode w ciągu za pomocą kopiowania wklejania. Kiedy określisz ciąg jako u''thestring ', kodowanie działa poprawnie.
Ben Liyanage
2
Działa tylko na Py3, ale jest elegancki.
hałaśliwy
7
Dla tych, którzy otrzymują ten sam błąd co @ Xodarap777: powinieneś najpierw .decode () ciąg, a dopiero potem zakodować. Na przykłads.decode('utf-8').encode('ascii', errors='ignore')
Spc_555
6

Twoje pytanie jest niejednoznaczne; pierwsze dwa zdania razem wzięte sugerują, że uważasz, że spacja i „kropka” nie są znakami ASCII. To jest niepoprawne. Wszystkie znaki takie, że ord (char) <= 127 są znakami ASCII. Na przykład twoja funkcja wyklucza te znaki! "# $% & \ '() * +, -. / Ale zawiera kilka innych, np. [] {}.

Cofnij się, pomyśl trochę i edytuj swoje pytanie, aby powiedzieć nam, co próbujesz zrobić, nie wspominając o słowie ASCII i dlaczego uważasz, że znaki takie, że ord (char)> = 128 są ignorowane. Ponadto: która wersja Pythona? Jakie jest kodowanie danych wejściowych?

Zwróć uwagę, że Twój kod odczytuje cały plik wejściowy jako pojedynczy ciąg, a Twój komentarz („świetne rozwiązanie”) do innej odpowiedzi sugeruje, że nie obchodzą Cię znaki nowej linii w danych. Jeśli twój plik zawiera dwie takie linie:

this is line 1
this is line 2

wynik byłby 'this is line 1this is line 2'... czy tego naprawdę chcesz?

Lepsze rozwiązanie obejmowałoby:

  1. lepsza nazwa dla funkcji filtrującej niż onlyascii
  2. rozpoznanie, że funkcja filtrująca musi jedynie zwrócić prawdziwą wartość, jeśli argument ma zostać zachowany:

    def filter_func(char):
        return char == '\n' or 32 <= ord(char) <= 126
    # and later:
    filtered_data = filter(filter_func, data).lower()
John Machin
źródło
Ta odpowiedź jest bardzo pomocna dla tych z nas, którzy przychodzą, aby zapytać o coś podobnego do OP, a Twoja proponowana odpowiedź jest pomocna w języku Python. Wydaje mi się jednak dziwne, że nie ma wydajniejszego rozwiązania problemu, tak jak go zinterpretowałeś (z czym często się spotykam) - znak po znaku, zajmuje to bardzo dużo czasu w bardzo dużym pliku.
Xodarap777
5

Możesz użyć następującego kodu, aby usunąć inne niż angielskie litery:

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

To wróci

123456790 ABC #%? . ()

Noha Elprince
źródło
1

Jeśli chcesz drukować znaki ascii, prawdopodobnie powinieneś poprawić swój kod, aby:

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

jest to odpowiednik string.printable(odpowiedź z @jterrace), z wyjątkiem braku zwrotów i tabulatorów ('\ t', '\ n', '\ x0b', '\ x0c' i '\ r'), ale nie odpowiada zakres na Twoje pytanie

joaquin
źródło
1
Nieco prostsze: lambda x: 32 <= ord (x) <= 126
jterrace
to nie to samo, co string.printable, ponieważ pomija string.whitespace, chociaż może tego chce OP, zależy od rzeczy takich jak \ n i \ t.
jterrace
@jterrace po prawej, zawiera spację (ord 32), ale bez zwrotów i tabulatorów
joaquin
tak, po prostu komentując „to jest odpowiednikiem string.printable”, ale nie jest to prawda
jterrace,
Zredagowałem odpowiedź, dzięki! pytanie OP jest mylące, jeśli nie przeczytasz go uważnie.
joaquin
1

Przedzieram się przez Fluent Python (Ramalho) - gorąco polecam. Zrozumienie listy jednoliniowe, zainspirowane rozdziałem 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
źródło
Nie pozwoliłoby to na stosowanie standardowych symboli ASCII, takich jak punktory, symbol stopni, symbol praw autorskich, symbol jena itp. Ponadto pierwszy przykład zawiera symbole niedrukowalne, takie jak BELL, co jest niepożądane.
SherylHohman