Odczytywanie i zapisywanie plików w języku Python w standardzie Unicode (UTF-8)

329

Mam problem z mózgiem w rozumieniu czytania i pisania tekstu do pliku (Python 2.4).

# The string, which has an a-acute in it.
ss = u'Capit\xe1n'
ss8 = ss.encode('utf8')
repr(ss), repr(ss8)

(„u'Capit \ xe1n” ”,„ Capit \ xc3 \ xa1n ”)

print ss, ss8
print >> open('f1','w'), ss8

>>> file('f1').read()
'Capit\xc3\xa1n\n'

Więc wpisuję Capit\xc3\xa1ndo mojego ulubionego edytora, w pliku f2.

Następnie:

>>> open('f1').read()
'Capit\xc3\xa1n\n'
>>> open('f2').read()
'Capit\\xc3\\xa1n\n'
>>> open('f1').read().decode('utf8')
u'Capit\xe1n\n'
>>> open('f2').read().decode('utf8')
u'Capit\\xc3\\xa1n\n'

Czego tu nie rozumiem? Najwyraźniej brakuje mi trochę magii (lub rozsądku). Co wpisuje się w pliki tekstowe, aby uzyskać prawidłowe konwersje?

To, czego naprawdę nie rozumiem tutaj, to, o co chodzi w reprezentacji UTF-8, jeśli nie możesz tak naprawdę przekonać Pythona do rozpoznania go, gdy pochodzi on z zewnątrz. Może powinienem po prostu JSON zrzucić ciąg i użyć go zamiast tego, ponieważ ma on reprezentatywną reprezentację! Co więcej, czy istnieje reprezentacja ASCII tego obiektu Unicode, który Python rozpozna i zdekoduje po przejściu z pliku? Jeśli tak, jak to zdobyć?

>>> print simplejson.dumps(ss)
'"Capit\u00e1n"'
>>> print >> file('f3','w'), simplejson.dumps(ss)
>>> simplejson.load(open('f3'))
u'Capit\xe1n'
Gregg Lind
źródło

Odpowiedzi:

110

W notacji

u'Capit\xe1n\n'

„\ xe1” reprezentuje tylko jeden bajt. „\ x” informuje, że „e1” jest w systemie szesnastkowym. Kiedy piszesz

Capit\xc3\xa1n

w twoim pliku jest „\ xc3”. Są to 4 bajty i w kodzie odczytujesz je wszystkie. Możesz to zobaczyć po ich wyświetleniu:

>>> open('f2').read()
'Capit\\xc3\\xa1n\n'

Możesz zobaczyć, że odwrotny ukośnik ucieka przez odwrotny ukośnik. Więc masz cztery bajty w swoim ciągu: „\”, „x”, „c” i „3”.

Edytować:

Jak zauważyli inni w swoich odpowiedziach, wystarczy wpisać znaki w edytorze, a następnie redaktor powinien obsłużyć konwersję do UTF-8 i zapisać ją.

Jeśli faktycznie masz ciąg znaków w tym formacie, możesz użyć string_escapekodeka, aby zdekodować go do normalnego ciągu:

In [15]: print 'Capit\\xc3\\xa1n\n'.decode('string_escape')
Capitán

Wynikiem jest łańcuch znaków zakodowany w UTF-8, w którym znak akcentowany jest reprezentowany przez dwa bajty zapisane \\xc3\\xa1w oryginalnym łańcuchu. Jeśli chcesz mieć ciąg Unicode, musisz ponownie dekodować za pomocą UTF-8.

Do twojej edycji: nie masz UTF-8 w swoim pliku. Aby zobaczyć, jak by to wyglądało:

s = u'Capit\xe1n\n'
sutf8 = s.encode('UTF-8')
open('utf-8.out', 'w').write(sutf8)

Porównaj zawartość pliku utf-8.outz zawartością pliku zapisanego w edytorze.


źródło
Jaki jest więc sens formatu zakodowanego w utf-8, jeśli Python może czytać w plikach, które go używają? Innymi słowy, czy istnieje jakaś reprezentacja ascii, którą Python będzie czytał w \ xc3 jako 1 bajt?
Gregg Lind
4
Odpowiedź na twoje pytanie „Jaki jest sens…” brzmi „Mu”. (ponieważ Python może odczytywać pliki zakodowane w UTF-8). W przypadku drugiego pytania: \ xc3 nie jest częścią zestawu ASCII. Być może zamiast tego masz na myśli „kodowanie 8-bitowe”. Jesteś zdezorientowany co do Unicode i kodowania; jest ok, wielu jest.
tzot
8
Spróbuj przeczytać to jako elementarz: joelonsoftware.com/articles/Unicode.html
tzot
Uwaga: u'\xe1'jest jednym kodem Unicode, U+00e1który może być reprezentowany przy użyciu 1 lub więcej bajtów w zależności od kodowania znaków (w utf-8 jest to 2 bajty). b'\xe1'to jeden bajt (liczba 225), jaka litera, jeśli jakąkolwiek może reprezentować, zależy od kodowania znaków użytego do jej zdekodowania, np. jest б( U+0431) w cp1251, с( U+0441) w cp866 itp.
jfs
11
To zadziwiające, jak wielu brytyjskich programistów mówi „po prostu używaj ascii”, a potem nie zdaje sobie sprawy, że znak £ to nie to. Większość nie wie, że ascii! = Lokalna strona kodowa (tj. Latin1).
Danny Staple,
712

Zamiast zadzierać z metodami kodowania i dekodowania, łatwiej jest mi określić kodowanie podczas otwierania pliku. ioModuł (dodawane w Pythonie 2.6) zapewnia io.openfunkcję, która zawiera parametr kodowania.

Użyj metody otwartej z iomodułu.

>>>import io
>>>f = io.open("test", mode="r", encoding="utf-8")

Następnie po wywołaniu funkcji read () f zwracany jest zakodowany obiekt Unicode.

>>>f.read()
u'Capit\xe1l\n\n'

Zauważ, że w Pythonie 3 io.openfunkcja jest aliasem dla wbudowanej openfunkcji. Wbudowana funkcja otwarta obsługuje tylko argument kodowania w Pythonie 3, a nie Python 2.

Edycja: Wcześniej ta odpowiedź zalecała moduł kodeków . Moduł kodeki mogą powodować problemy podczas mieszania read()ireadline() tak ta odpowiedź teraz zaleca io moduł zamiast.

Użyj metody otwartej z modułu kodeków.

>>>import codecs
>>>f = codecs.open("test", "r", "utf-8")

Następnie po wywołaniu funkcji read () f zwracany jest zakodowany obiekt Unicode.

>>>f.read()
u'Capit\xe1l\n\n'

Jeśli znasz kodowanie pliku, użycie pakietu kodeków będzie znacznie mniej skomplikowane.

Zobacz http://docs.python.org/library/codecs.html#codecs.open

Tim Swast
źródło
74
Działa doskonale również do pisania plików, zamiast open(file,'w')do codecs.open(file,'w','utf-8')rozwiązania
Matt Connolly
1
Oto odpowiedź, której szukałem :)
Justin
6
Czy codecs.open(...)metoda jest również w pełni zgodna ze with open(...):stylem, w którym withdbanie o zamknięcie pliku jest już zrobione? Wygląda na to, że i tak działa.
try-catch-wreszcie
2
@ try-catch-wreszcie Tak. Używam with codecs.open(...) as f:cały czas.
Tim Swast
6
Chciałbym móc to głosować sto razy. Po kilku dniach udręki z powodu problemów z kodowaniem spowodowanych dużą ilością mieszanych danych i czytaniu o kodowaniu z zezem, ta odpowiedź jest jak woda na pustyni. Chciałbym zobaczyć to wcześniej.
Mike Girard
45

Teraz wszystko czego potrzebujesz w Python3 to open(Filename, 'r', encoding='utf-8')

[Edytuj w dniu 02.02.2016 r. W celu uzyskania wyjaśnień]

Python3 dodał parametr kodowania do swojej funkcji otwartej. Tutaj zebrano następujące informacje o funkcji otwartej: https://docs.python.org/3/library/functions.html#open

open(file, mode='r', buffering=-1, 
      encoding=None, errors=None, newline=None, 
      closefd=True, opener=None)

Kodowanie to nazwa kodowania używanego do dekodowania lub kodowania pliku. Tego należy używać tylko w trybie tekstowym. Domyślne kodowanie zależy od platformy (cokolwiek zwraca locale.getpreferredencoding () ), ale można użyć dowolnego kodowania tekstu obsługiwanego przez Python. Zobacz moduł kodeków , aby uzyskać listę obsługiwanych kodowań.

Tak więc, dodając encoding='utf-8'jako parametr do funkcji open, odczyt i zapis pliku odbywa się jako utf8 (który jest teraz również domyślnym kodowaniem wszystkiego, co dzieje się w Pythonie).

Dakusan
źródło
Czy mógłby Pan uściślić swoją odpowiedź, dodając nieco więcej opisu oferowanego rozwiązania?
abarisone
2
Wygląda na to, że jest dostępny w Pythonie 2 przy użyciu modułu kodeków - codecs.open('somefile', encoding='utf-8') stackoverflow.com/a/147756/149428
Taylor Edmiston
18

Więc znalazłem rozwiązanie tego, czego szukam, a mianowicie:

print open('f2').read().decode('string-escape').decode("utf-8")

Przydaje się kilka niezwykłych kodeków. Ten konkretny odczyt umożliwia pobranie reprezentacji UTF-8 z poziomu Pythona, skopiowanie ich do pliku ASCII i odczytanie ich do Unicode. Pod dekodą „string-escape” ukośniki nie zostaną podwojone.

Pozwala to na taką podróż w obie strony, jaką sobie wyobrażałem.

Gregg Lind
źródło
1
Dobra odpowiedź, przetestowałem oba rozwiązania (codecs.open(file,"r","utf-8")i po prostu open(file,"r").read().decode("utf-8")oba działały idealnie.
Eagle
Dostaję „TypeError: oczekiwany obiekt str, bytes lub os.PathLike, a nie _io.TextIOWrapper”, jakiś pomysł, dlaczego?
JinSnow
Myślę, że biorąc pod uwagę liczbę głosów pozytywnych, dobrym pomysłem byłoby zaakceptowanie drugiej odpowiedzi :)
Jacquot
14
# -*- encoding: utf-8 -*-

# converting a unknown formatting file in utf-8

import codecs
import commands

file_location = "jumper.sub"
file_encoding = commands.getoutput('file -b --mime-encoding %s' % file_location)

file_stream = codecs.open(file_location, 'r', file_encoding)
file_output = codecs.open(file_location+"b", 'w', 'utf-8')

for l in file_stream:
    file_output.write(l)

file_stream.close()
file_output.close()
Ricardo
źródło
14

Właściwie to działało dla mnie do odczytu pliku z kodowaniem UTF-8 w Pythonie 3.2:

import codecs
f = codecs.open('file_name.txt', 'r', 'UTF-8')
for line in f:
    print(line)
Sina
źródło
6

Aby przeczytać ciąg Unicode, a następnie wysłać do HTML, zrobiłem to:

fileline.decode("utf-8").encode('ascii', 'xmlcharrefreplace')

Przydatne w przypadku serwerów http zasilanych przez Pythona.

praj
źródło
6

Natknąłeś się na ogólny problem z kodowaniem: Jak mogę stwierdzić, w którym kodowaniu jest plik?

Odpowiedź: Nie możesz tego zrobić, chyba że format pliku to zapewnia. Na przykład XML zaczyna się od:

<?xml encoding="utf-8"?>

Ten nagłówek został starannie wybrany, aby można go było odczytać bez względu na kodowanie. W twoim przypadku nie ma takiej wskazówki, dlatego ani twój redaktor, ani Python nie mają pojęcia, co się dzieje. Dlatego musisz użyć codecsmodułu i użyćcodecs.open(path,mode,encoding) który zapewnia brakujący bit w Pythonie.

Jeśli chodzi o edytor, musisz sprawdzić, czy oferuje on sposób na ustawienie kodowania pliku.

Celem UTF-8 jest możliwość kodowania 21-bitowych znaków (Unicode) jako 8-bitowego strumienia danych (ponieważ jest to jedyna rzecz, którą wszystkie komputery na świecie mogą sobie poradzić). Ponieważ jednak większość systemów operacyjnych pochodzi z epoki Unicode, nie mają one odpowiednich narzędzi do dołączania informacji o kodowaniu do plików na dysku twardym.

Kolejnym problemem jest reprezentacja w Pythonie. To doskonale wyjaśniono w komentarzu heikogerlach . Musisz zrozumieć, że twoja konsola może wyświetlać tylko ASCII. Aby wyświetlić Unicode lub cokolwiek> = kod znakowy 128, musi użyć jakiegoś sposobu zmiany znaczenia. W edytorze nie wolno wpisywać uciekającego łańcucha wyświetlanego, ale jego znaczenie (w tym przypadku należy wprowadzić umlaut i zapisać plik).

To powiedziawszy, możesz użyć funkcji eval () Pythona, aby przekształcić łańcuch znaków w ciąg znaków:

>>> x = eval("'Capit\\xc3\\xa1n\\n'")
>>> x
'Capit\xc3\xa1n\n'
>>> x[5]
'\xc3'
>>> len(x[5])
1

Jak widać, ciąg „\ xc3” został przekształcony w pojedynczy znak. Jest to teraz 8-bitowy ciąg kodowany w UTF-8. Aby uzyskać Unicode:

>>> x.decode('utf-8')
u'Capit\xe1n\n'

Gregg Lind zapytał: Myślę, że brakuje tutaj niektórych elementów: plik f2 zawiera: hex:

0000000: 4361 7069 745c 7863 335c 7861 316e  Capit\xc3\xa1n

codecs.open('f2','rb', 'utf-8'), na przykład czyta je wszystkie w osobnych znakach (oczekiwane) Czy istnieje jakiś sposób zapisu do pliku w ASCII, który by działał?

Odpowiedź: To zależy od tego, co masz na myśli. ASCII nie może reprezentować znaków> 127. Dlatego potrzebujesz sposobu, aby powiedzieć „kilka następnych znaków oznacza coś specjalnego”, co robi sekwencja „\ x”. Mówi: Następne dwa znaki to kod jednego znaku. „\ u” robi to samo, używając czterech znaków do kodowania Unicode do 0xFFFF (65535).

Nie możesz więc bezpośrednio pisać Unicode do ASCII (ponieważ ASCII po prostu nie zawiera tych samych znaków). Możesz pisać jako znaki ucieczki (jak w f2); w takim przypadku plik może być reprezentowany jako ASCII. Możesz też napisać go jako UTF-8, w takim przypadku potrzebujesz 8-bitowego bezpiecznego strumienia.

Użyte rozwiązanie decode('string-escape')działa, ale musisz pamiętać, ile pamięci zużywasz: trzy razy więcej niż używaszcodecs.open() .

Pamiętaj, że plik jest tylko sekwencją bajtów z 8 bitami. Ani bity, ani bajty nie mają znaczenia. To ty mówisz „65” oznacza „A”. Ponieważ \xc3\xa1powinno stać się „à”, ale komputer nie ma możliwości wiedzieć, musisz to powiedzieć, określając kodowanie, które zostało użyte podczas zapisywania pliku.

Aaron Digulla
źródło
Myślę, że brakuje tutaj niektórych elementów: plik f2 zawiera: hex: 0000000: 4361 7069 745c 7863 335c 7861 316e 0a Capit \ xc3 \ xa1n. codecs.open ('f2', 'rb', 'utf-8'), na przykład, czyta je wszystkie w osobnych znakach (oczekiwane) Czy jest jakiś sposób na zapisanie pliku w ascii, który by działał?
Gregg Lind
6

z wyjątkiem tego codecs.open(), że można używać io.open()do pracy z Python2 lub Python3 do odczytu / zapisu pliku Unicode

przykład

import io

text = u'á'
encoding = 'utf8'

with io.open('data.txt', 'w', encoding=encoding, newline='\n') as fout:
    fout.write(text)

with io.open('data.txt', 'r', encoding=encoding, newline='\n') as fin:
    text2 = fin.read()

assert text == text2
Ryan
źródło
Tak, użycie io jest lepsze; Ale pisałem ze stwierdzeniem jak ten with io.open('data.txt', 'w', 'utf-8') as file:i got błąd: TypeError: an integer is required. Po tym, jak się zmieniłem with io.open('data.txt', 'w', encoding='utf-8') as file:i działało.
Evan Hu
5

Cóż, twój ulubiony edytor tekstu nie zdaje sobie sprawy, że \xc3\xa1powinny to być dosłowne znaki, ale interpretuje je jako tekst. Dlatego w ostatnim wierszu pojawia się podwójny ukośnik odwrotny - jest to teraz prawdziwy odwrotny ukośnik + xc3itp. W twoim pliku.

Jeśli chcesz czytać i zapisywać zakodowane pliki w Pythonie, najlepiej użyj modułu kodeków .

Wklejanie tekstu między terminalem a aplikacjami jest trudne, ponieważ nie wiesz, który program zinterpretuje tekst za pomocą jakiego kodowania. Możesz spróbować:

>>> s = file("f1").read()
>>> print unicode(s, "Latin-1")
Capitán

Następnie wklej ten ciąg do edytora i upewnij się, że przechowuje go za pomocą Latin-1. Przy założeniu, że schowek nie niszczy sznurka, podróż w obie strony powinna działać.

Torsten Marek
źródło
4

Sekwencja \ x .. jest specyficzna dla Pythona. To nie jest uniwersalna sekwencja bajtów.

To, jak faktycznie wprowadzasz kodowanie UTF-8 w formacie innym niż ASCII, zależy od systemu operacyjnego i / lub edytora. Oto jak to zrobić w systemie Windows . Dla OS X, aby wprowadzić z ostrym akcentem można po prostu hit + , a następnie , i prawie wszystkie edytory tekstu na poparcie OS X UTF-8.optionEA

.ıu
źródło
3

Możesz także ulepszyć oryginalną open()funkcję do pracy z plikami Unicode, zastępując ją w miejscu za pomocą partialfunkcji. Zaletą tego rozwiązania jest to, że nie musisz zmieniać żadnego starego kodu. Jest przezroczysty.

import codecs
import functools
open = functools.partial(codecs.open, encoding='utf-8')
hipertracker
źródło
1

Próbowałem parsować iCal przy użyciu Python 2.7.9:

z kalendarza importu icalendar

Ale dostawałem:

 Traceback (most recent call last):
 File "ical.py", line 92, in parse
    print "{}".format(e[attr])
UnicodeEncodeError: 'ascii' codec can't encode character u'\xe1' in position 7: ordinal not in range(128)

i zostało to naprawione za pomocą:

print "{}".format(e[attr].encode("utf-8"))

(Teraz można wydrukować liké á böss.)

Alexx Roche
źródło
0

Znalazłem najprostsze podejście, zmieniając domyślne kodowanie całego skryptu na „UTF-8”:

import sys
reload(sys)
sys.setdefaultencoding('utf8')

każdy open, printlub inne oświadczenie będzie po prostu używać utf8.

Działa przynajmniej dla Python 2.7.9.

Thx idzie na https://markhneedham.com/blog/2015/05/21/python-unicodeencodeerror-ascii-codec-cant-encode-character-uxfc-in-position-11-ordinal-not-in-range128/ ( spójrz na koniec).

dr0i
źródło