Jak sprawić, by interpreter Pythona poprawnie obsługiwał znaki spoza ASCII w operacjach na łańcuchach?

104

Mam ciąg, który wygląda tak:

6 918 417 712

Jasnym sposobem przycięcia tego ciągu (jak rozumiem Python) jest po prostu powiedzenie, że ciąg znajduje się w zmiennej o nazwie s, otrzymujemy:

s.replace('Â ', '')

To powinno załatwić sprawę. Ale oczywiście narzeka, że ​​znak spoza ASCII '\xc2'w pliku blabla.py nie jest zakodowany.

Nigdy nie mogłem zrozumieć, jak przełączać się między różnymi kodowaniami.

Oto kod, tak naprawdę jest taki sam jak powyżej, ale teraz jest w kontekście. Plik jest zapisywany jako UTF-8 w notatniku i ma następujący nagłówek:

#!/usr/bin/python2.4
# -*- coding: utf-8 -*-

Kod:

f = urllib.urlopen(url)

soup = BeautifulSoup(f)

s = soup.find('div', {'id':'main_count'})

#making a print 's' here goes well. it shows 6Â 918Â 417Â 712

s.replace('Â ','')

save_main_count(s)

Nie idzie dalej niż s.replace...

adergaard
źródło
1
Do tej pory wypróbowałem wszystkie 4 odpowiedzi. Nie idź. Nadal pojawia się błąd UnicodeDecodeError: kodek „ascii” nie może zdekodować bajtu 0xc2 na pozycji 1: numer porządkowy poza zakresem (128)
adergaard
Twój ciąg znaków Unicode musi być poprzedzonyu
SilentGhost
@SilentGhost: jak widać, nie ma możliwości upewnienia się, że jest to ciąg znaków Unicode. Otrzymuję ciąg, który ma zawartość pokazaną powyżej, ale zawiera ciągi inne niż ASCII. To jest prawdziwy problem. Domyślam się, że jest to Unicode, ponieważ nie ma go w pierwszym 128.
adergaard
Błąd nie ma nic wspólnego z przychodzącym ciągiem. To ciąg znaków w Twoim kodzie, który wywołuje ten błąd!
SilentGhost
2
Założę się, że właśnie dlatego Python 3 tak rygorystycznie podchodzi do różnicy między łańcuchami a sekwencjami bajtów, aby uniknąć tego rodzaju zamieszania.
Mark Ransom

Odpowiedzi:

84

Python 2 używa asciidomyślnego kodowania dla plików źródłowych, co oznacza, że ​​musisz określić inne kodowanie na początku pliku, aby użyć znaków Unicode innych niż ASCII w literałach. Python 3 używa utf-8domyślnego kodowania plików źródłowych, więc jest to mniejszy problem.

Zobacz: http://docs.python.org/tutorial/interpreter.html#source-code-encoding

Aby włączyć kodowanie źródła utf-8, powinno to znaleźć się w jednym z dwóch górnych wierszy:

# -*- coding: utf-8 -*-

Powyższe znajduje się w dokumentacji, ale działa to również:

# coding: utf-8

Dodatkowe uwagi:

  • Plik źródłowy należy również zapisać przy użyciu prawidłowego kodowania w edytorze tekstu.

  • W Pythonie 2 literał Unicode musi mieć uprzed sobą znak , jak w przypadku, s.replace(u"Â ", u"")ale w Pythonie 3 po prostu użyj cudzysłowów. W Pythonie 2 można from __future__ import unicode_literalsuzyskać zachowanie Pythona 3, ale należy pamiętać, że ma to wpływ na cały bieżący moduł.

  • s.replace(u"Â ", u"")również zakończy się niepowodzeniem, jeśli snie jest ciągiem znaków Unicode.

  • string.replace zwraca nowy ciąg i nie edytuje go w miejscu, więc upewnij się, że używasz również wartości zwracanej

Jason S.
źródło
4
Właściwie potrzebujesz tylko # coding: utf-8. -*-nie służy do dekoracji, ale prawdopodobnie nigdy jej nie będziesz potrzebować. Myślę, że był tam dla starych muszli.
fmalina
157
def removeNonAscii(s): return "".join(filter(lambda x: ord(x)<128, s))

edycja: moim pierwszym impulsem jest zawsze użycie filtra, ale wyrażenie generatora jest bardziej wydajne w pamięci (i krótsze) ...

def removeNonAscii(s): return "".join(i for i in s if ord(i)<128)

Pamiętaj, że gwarantuje to działanie z kodowaniem UTF-8 (ponieważ wszystkie bajty w znakach wielobajtowych mają najwyższy bit ustawiony na 1).

fortran
źródło
1
Otrzymuję: TypeError: ord () oczekiwano znaku, ale znaleziono ciąg o długości 2
Ivelin
@Ivelin to dlatego, że „znak” nie jest interpretowany jako właściwy Unicode ... sprawdź, czy ciąg źródłowy jest poprzedzony prefiksem, ujeśli jest to literał.
fortran
35
>>> unicode_string = u"hello aåbäcö"
>>> unicode_string.encode("ascii", "ignore")
'hello abc'
truppo
źródło
4
Widzę głosy, które otrzymujesz, ale kiedy próbuję, wyświetla się komunikat: Nie. UnicodeDecodeError: kodek „ascii” nie może zdekodować bajtu 0xc2 na pozycji 1: numer porządkowy poza zakresem (128). Czy to możliwe, że mój oryginalny ciąg nie jest w formacie Unicode? W każdym razie. to potrzebuje
adergaard
2
Fajnie dzięki. Czy mogę zasugerować użycie .decode () na wyniku, aby uzyskać go w oryginalnym kodowaniu?
AkiRoss
Jeśli otrzymujesz UnicodeDecodeError: 'ascii', spróbuj przekonwertować ciąg na format '' UTF-8 'przed zastosowaniem funkcji kodowania.
Sateesh
16

Poniższy kod zamieni wszystkie znaki spoza zestawu ASCII na znaki zapytania.

"".join([x if ord(x) < 128 else '?' for x in s])
Wizja
źródło
Z ciekawości chciałem wiedzieć, czy jest jakiś konkretny powód, aby zastąpić go znakiem zapytania?
Mohsin
6

Korzystanie z Regex:

import re

strip_unicode = re.compile("([^-_a-zA-Z0-9!@#%&=,/'\";:~`\$\^\*\(\)\+\[\]\.\{\}\|\?\<\>\\]+|[^\s]+)")
print strip_unicode.sub('', u'6Â 918Â 417Â 712')
Akoi Meexx
źródło
5

O wiele za późno na odpowiedź, ale oryginalny ciąg był w UTF-8, a '\ xc2 \ xa0' to UTF-8 dla PRZESTRZENI BEZ PRZERW. Po prostu zdekoduj oryginalny ciąg jako s.decode('utf-8')(\ xa0 wyświetla się jako spacja, gdy dekodowany jest nieprawidłowo jako Windows-1252 lub latin-1:

Przykład (Python 3)

s = b'6\xc2\xa0918\xc2\xa0417\xc2\xa0712'
print(s.decode('latin-1')) # incorrectly decoded
u = s.decode('utf8') # correctly decoded
print(u)
print(u.replace('\N{NO-BREAK SPACE}','_'))
print(u.replace('\xa0','-')) # \xa0 is Unicode for NO-BREAK SPACE

Wynik

6 918 417 712
6 918 417 712
6_918_417_712
6-918-417-712
Mark Tolonen
źródło
3
#!/usr/bin/env python
# -*- coding: utf-8 -*-

s = u"6Â 918Â 417Â 712"
s = s.replace(u"Â", "") 
print s

To zostanie wydrukowane 6 918 417 712

Izajasz
źródło
Nie. UnicodeDecodeError: kodek „ascii” nie może zdekodować bajtu 0xc2 na pozycji 1: numer porządkowy poza zakresem (128). Czy to możliwe, że mój oryginalny ciąg nie jest w formacie Unicode? W każdym razie. Prawdopodobnie robię coś złego.
adergaard
@adergaard, czy dodałeś # - - kodowanie: utf-8 - - na górze pliku źródłowego?
Nadia Alramli
Tak, zobacz ponownie górę tej strony, zredagowałem questoin i umieściłem kod i komentarze w nagłówku. Dziękuję za Twoją pomoc.
adergaard
Myślę, że będziesz musiał dowiedzieć się, jak uzyskać ciągi znaków z dokumentu html lub xml w Unicode. Więcej informacji na ten temat tutaj: diveintopython.org/xml_processing/unicode.html
Izajasz
2

Wiem, że to stary wątek, ale poczułem się zmuszony wspomnieć o metodzie tłumaczenia, która jest zawsze dobrym sposobem na zastąpienie wszystkich kodów znaków powyżej 128 (lub innych, jeśli to konieczne).

Zastosowanie : str. translate ( table [, deletechars] )

>>> trans_table = ''.join( [chr(i) for i in range(128)] + [' '] * 128 )

>>> 'Résultat'.translate(trans_table)
'R sultat'
>>> '6Â 918Â 417Â 712'.translate(trans_table)
'6  918  417  712'

Począwszy od Pythona 2.6 , możesz także ustawić tabelę na None i użyć deletechars, aby usunąć znaki, których nie chcesz, jak w przykładach pokazanych w standardowej dokumentacji na http://docs.python.org/library/stdtypes. html .

W przypadku łańcuchów Unicode tablica translacji nie jest łańcuchem 256-znakowym, ale dyktatem z ord () odpowiednich znaków jako kluczy. W każdym razie uzyskanie prawidłowego ciągu ascii z ciągu znaków Unicode jest dość proste, przy użyciu metody wspomnianej powyżej przez truppo, a mianowicie: unicode_string.encode ("ascii", "ignore")

Podsumowując, jeśli z jakiegoś powodu absolutnie potrzebujesz uzyskać ciąg ascii (na przykład, gdy zgłaszasz standardowy wyjątek za pomocą raise Exception, ascii_message), możesz użyć następującej funkcji:

trans_table = ''.join( [chr(i) for i in range(128)] + ['?'] * 128 )
def ascii(s):
    if isinstance(s, unicode):
        return s.encode('ascii', 'replace')
    else:
        return s.translate(trans_table)

Zaletą translate jest to, że można faktycznie konwertować znaki akcentowane na odpowiednie znaki ascii bez akcentu zamiast po prostu je usuwać lub zastępować znakiem „?”. Jest to często przydatne, na przykład do celów indeksowania.

Louis LC
źródło
Otrzymuję: TypeError: mapowanie znaków musi zwracać liczbę całkowitą, None lub Unicode
Ivelin
1
s.replace(u'Â ', '')              # u before string is important

i .pyunicode pliku.

SilentGhost
źródło
1

To brudny hack, ale może zadziałać.

s2 = ""
for i in s:
    if ord(i) < 128:
        s2 += i
Corey D.
źródło
0

Na ile to było warte, mój zestaw postaci był utf-8i włączyłem klasyczną # -*- coding: utf-8 -*-linię " ".

Jednak odkryłem, że nie mam Universal Newlines podczas odczytywania tych danych ze strony internetowej.

Mój tekst miał dwa słowa oddzielone znakiem „ \r\n”. Ja tylko rozszczepiałem \ni wymieniłem "\n".

Kiedy przeszedłem przez pętlę i zobaczyłem omawianą postać, zdałem sobie sprawę z błędu.

Może więc również znajdować się w zestawie znaków ASCII , ale znak, którego się nie spodziewałeś.

Dolina górska
źródło