Unicode (UTF-8) che legge e scrive su file in Python


330

Sto riscontrando un fallimento del cervello nella comprensione della lettura e della scrittura di testo in un file (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'

Quindi scrivo nel Capit\xc3\xa1nmio editor preferito, nel file f2.

Poi:

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

Cosa non capisco qui? Chiaramente c'è un po 'vitale di magia (o buon senso) che mi manca. Cosa digita in file di testo per ottenere le conversioni corrette?

Quello che davvero non riesco a fare grok qui, è qual è il punto della rappresentazione UTF-8, se non riesci davvero a far riconoscere Python, quando viene dall'esterno. Forse dovrei solo JSON scaricare la stringa e usarla invece, dal momento che ha una rappresentazione asciuttibile! Più precisamente, esiste una rappresentazione ASCII di questo oggetto Unicode che Python riconoscerà e decodificherà quando proviene da un file? In tal caso, come posso ottenerlo?

>>> print simplejson.dumps(ss)
'"Capit\u00e1n"'
>>> print >> file('f3','w'), simplejson.dumps(ss)
>>> simplejson.load(open('f3'))
u'Capit\xe1n'

Risposte:


110

Nella notazione

u'Capit\xe1n\n'

"\ xe1" rappresenta solo un byte. "\ x" ti dice che "e1" è in esadecimale. Quando scrivi

Capit\xc3\xa1n

nel tuo file hai "\ xc3" al suo interno. Sono 4 byte e nel tuo codice li leggi tutti. Puoi vederlo quando li visualizzi:

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

Puoi vedere che la barra rovesciata è sfuggita da una barra rovesciata. Quindi hai quattro byte nella tua stringa: "\", "x", "c" e "3".

Modificare:

Come altri hanno sottolineato nelle loro risposte, dovresti semplicemente inserire i caratteri nell'editor e il tuo editor dovrebbe quindi gestire la conversione in UTF-8 e salvarlo.

Se in realtà hai una stringa in questo formato puoi usare il string_escapecodec per decodificarlo in una stringa normale:

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

Il risultato è una stringa codificata in UTF-8 in cui il carattere accentato è rappresentato dai due byte scritti \\xc3\\xa1nella stringa originale. Se vuoi avere una stringa unicode devi decodificare di nuovo con UTF-8.

Per la tua modifica: non hai UTF-8 nel tuo file. Per vedere effettivamente come sarebbe:

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

Confronta il contenuto del file utf-8.outcon il contenuto del file che hai salvato con il tuo editor.


Quindi, qual è il punto del formato codificato utf-8 se python è in grado di leggere nei file utilizzandolo? In altre parole, c'è qualche rappresentazione ASCII che Python leggerà in \ xc3 come 1 byte?
Gregg Lind,

4
La risposta alla tua domanda "Allora, qual è il punto ..." è "Mu". (poiché Python è in grado di leggere file codificati in UTF-8). Per la tua seconda domanda: \ xc3 non fa parte del set ASCII. Forse intendi invece "codifica a 8 bit". Sei confuso su Unicode e codifiche; va bene, molti lo sono.
martedì

8
Prova a leggere questo come primer: joelonsoftware.com/articles/Unicode.html
tzot

nota: u'\xe1'è un punto di codice Unicode U+00e1che può essere rappresentato usando 1 o più byte a seconda della codifica dei caratteri (sono 2 byte in utf-8). b'\xe1'è un byte (un numero 225), quale lettera può rappresentare dipende dalla codifica dei caratteri utilizzata per decodificarla, ad esempio, è б( U+0431) in cp1251, с( U+0441) in cp866, ecc.
jfs

11
È sorprendente il numero di programmatori britannici che dicono "usa solo ASCII" e poi non riescono a capire che il segno £ non lo è. La maggior parte non è a conoscenza del fatto che ascii! = Codepage locale (ovvero latin1).
Danny Staple,

712

Invece di pasticciare con i metodi di codifica e decodifica, trovo più facile specificare la codifica quando si apre il file. Il iomodulo (aggiunto in Python 2.6) fornisce una io.openfunzione, che ha un parametro di codifica.

Utilizzare il metodo aperto dal iomodulo.

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

Quindi, dopo aver chiamato la funzione read () di f, viene restituito un oggetto Unicode codificato.

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

Si noti che in Python 3, la io.openfunzione è un alias per la openfunzione integrata. La funzione aperta integrata supporta solo l'argomento di codifica in Python 3, non Python 2.

Modifica: in precedenza questa risposta raccomandava il modulo codec . Il modulo codec può causare problemi durante il missaggio read()ereadline() , quindi, questa risposta ora consiglia invece il modulo io .

Utilizzare il metodo open dal modulo codecs.

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

Quindi, dopo aver chiamato la funzione read () di f, viene restituito un oggetto Unicode codificato.

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

Se conosci la codifica di un file, l'uso del pacchetto codec sarà molto meno confuso.

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


74
Funziona perfettamente per la scrittura di file troppo, invece di open(file,'w')fare codecs.open(file,'w','utf-8')risolto
Matt Connolly

1
Questa è la risposta che stavo cercando :)
Justin il

6
Il codecs.open(...)metodo è anche pienamente conforme allo with open(...):stile, a cui withimporta di chiudere il file dopo aver fatto tutto? Sembra funzionare comunque.
Try-catch-finalmente

2
@ try-catch-finalmente Sì. Lo uso with codecs.open(...) as f:sempre.
Tim Swast,

6
Vorrei poter votare questo cento volte. Dopo aver sofferto per diversi giorni per problemi di codifica causati da molti dati misti e aver letto a occhi aperti sulla codifica, questa risposta è come l'acqua in un deserto. Vorrei averlo visto prima.
Mike Girard,

45

Ora tutto ciò che serve in Python3 è open(Filename, 'r', encoding='utf-8')

[Modifica il 10-02-2016 per il chiarimento richiesto]

Python3 ha aggiunto il parametro di codifica alla sua funzione aperta. Le seguenti informazioni sulla funzione aperta sono raccolte da qui: 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)

Codifica è il nome della codifica utilizzata per decodificare o codificare il file. Questo dovrebbe essere usato solo in modalità testo. La codifica predefinita dipende dalla piattaforma (qualunque sia il valore locale.getpreferredencoding () restituito), ma è possibile utilizzare qualsiasi codifica di testo supportata da Python. Vedi il modulo codec per l'elenco delle codifiche supportate.

Quindi aggiungendo encoding='utf-8'come parametro alla funzione open, la lettura e la scrittura del file sono tutte eseguite come utf8 (che ora è anche la codifica predefinita di tutto ciò che viene fatto in Python).


Potresti per favore elaborare di più la tua risposta aggiungendo un po 'più di descrizione della soluzione che offri?
Abarisone,

2
Sembra che sia disponibile in Python 2 utilizzando il modulo codecs - codecs.open('somefile', encoding='utf-8') stackoverflow.com/a/147756/149428
Taylor Edmiston

18

Quindi, ho trovato una soluzione per quello che sto cercando, che è:

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

Ci sono alcuni codec insoliti che sono utili qui. Questa particolare lettura consente di prendere le rappresentazioni UTF-8 dall'interno di Python, copiarle in un file ASCII e farle leggere in Unicode. Sotto la decodifica "string-escape", le barre non saranno raddoppiate.

Ciò consente il tipo di andata e ritorno che stavo immaginando.


1
Buona risposta, sono stato testato entrambe le soluzioni (codecs.open(file,"r","utf-8")e semplicemente open(file,"r").read().decode("utf-8")ed entrambi hanno funzionato perfettamente.
Eagle,

Ricevo un "TypeError: atteso str, byte o oggetto os.PathLike, non _io.TextIOWrapper" qualche idea del perché?
JinSnow

Penso che, considerando il numero di voti, sarebbe una grande idea accettare la seconda risposta :)
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()

14

In realtà, questo ha funzionato per me per la lettura di un file con codifica UTF-8 in Python 3.2:

import codecs
f = codecs.open('file_name.txt', 'r', 'UTF-8')
for line in f:
    print(line)

6

Per leggere in una stringa Unicode e quindi inviare a HTML, ho fatto questo:

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

Utile per server http alimentati a Python.


6

Ti sei imbattuto nel problema generale delle codifiche: come posso sapere in quale codifica si trova un file?

Risposta: non è possibile a meno che il formato del file non preveda questo. XML, ad esempio, inizia con:

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

Questa intestazione è stata scelta con cura in modo che possa essere letta indipendentemente dalla codifica. Nel tuo caso, non esiste un tale suggerimento, quindi né il tuo editor né Python hanno idea di cosa stia succedendo. Pertanto, è necessario utilizzare il codecsmodulo e utilizzarecodecs.open(path,mode,encoding) che fornisce il bit mancante in Python.

Per quanto riguarda l'editor, è necessario verificare se offre un modo per impostare la codifica di un file.

Il punto di UTF-8 è di essere in grado di codificare i caratteri a 21 bit (Unicode) come un flusso di dati a 8 bit (perché questa è l'unica cosa che tutti i computer al mondo sono in grado di gestire). Ma poiché la maggior parte dei sistemi operativi è precedente all'era Unicode, non dispongono di strumenti adeguati per collegare le informazioni di codifica ai file sul disco rigido.

Il prossimo numero è la rappresentazione in Python. Questo è spiegato perfettamente nel commento di Heikogerlach . Devi capire che la tua console può visualizzare solo ASCII. Per visualizzare Unicode o altro> = charcode 128, è necessario utilizzare alcuni metodi di escape. Nel tuo editor, non devi digitare la stringa di visualizzazione con escape ma cosa significa la stringa (in questo caso, devi inserire la umlaut e salvare il file).

Detto questo, puoi usare la funzione Python eval () per trasformare una stringa con escape in una stringa:

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

Come puoi vedere, la stringa "\ xc3" è stata trasformata in un singolo carattere. Questa è ora una stringa a 8 bit, codificata UTF-8. Per ottenere Unicode:

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

Gregg Lind ha chiesto: Penso che manchino alcuni pezzi: il file f2 contiene: hex:

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

codecs.open('f2','rb', 'utf-8'), ad esempio, li legge tutti in caratteri separati (previsti) Esiste un modo per scrivere su un file in ASCII che funzioni?

Risposta: dipende da cosa intendi. ASCII non può rappresentare caratteri> 127. Quindi hai bisogno di un modo per dire "i prossimi caratteri significano qualcosa di speciale" che è ciò che fa la sequenza "\ x". Dice: I prossimi due caratteri sono il codice di un singolo carattere. "\ u" fa lo stesso usando quattro caratteri per codificare Unicode fino a 0xFFFF (65535).

Quindi non puoi scrivere direttamente Unicode su ASCII (perché ASCII semplicemente non contiene gli stessi caratteri). Puoi scriverlo come escape di stringa (come in f2); in questo caso, il file può essere rappresentato come ASCII. Oppure puoi scriverlo come UTF-8, nel qual caso hai bisogno di un flusso sicuro a 8 bit.

La soluzione utilizzata decode('string-escape')funziona, ma è necessario essere consapevoli della quantità di memoria utilizzata: tre volte la quantità di utilizzocodecs.open() .

Ricorda che un file è solo una sequenza di byte con 8 bit. Né i bit né i byte hanno un significato. Sei tu che dici "65 significa 'A'". Dato che \xc3\xa1dovrebbe diventare "à" ma il computer non ha alcun mezzo per saperlo, devi dirlo specificando la codifica che è stata utilizzata durante la scrittura del file.


Penso che manchino alcuni pezzi: il file f2 contiene: hex: 0000000: 4361 7069 745c 7863 335c 7861 316e 0a Capit \ xc3 \ xa1n. codecs.open ('f2', 'rb', 'utf-8'), ad esempio, li legge tutti in caratteri separati (previsto). C'è un modo per scrivere su un file in ASCII che funzionerebbe?
Gregg Lind,

6

fatta eccezione per codecs.open(), si può usare io.open()per lavorare con Python2 o Python3 per leggere / scrivere file unicode

esempio

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


Sì, usare io è meglio; Ma ho scritto il con dichiarazione come questa with io.open('data.txt', 'w', 'utf-8') as file:e ottenuto un errore: TypeError: an integer is required. Dopo sono passato a with io.open('data.txt', 'w', encoding='utf-8') as file:e ha funzionato.
Evan Hu

5

Bene, il tuo editor di testo preferito non si rende conto che \xc3\xa1dovrebbero essere letterali di personaggi, ma li interpreta come testo. Ecco perché ottieni le doppie barre rovesciate nell'ultima riga: ora è una vera barra rovesciata + xc3, ecc. Nel tuo file.

Se vuoi leggere e scrivere file codificati in Python, usa al meglio il modulo codecs .

Incollare il testo tra il terminale e le applicazioni è difficile, perché non sai quale programma interpreterà il tuo testo usando quale codifica. Puoi provare quanto segue:

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

Quindi incolla questa stringa nel tuo editor e assicurati che la memorizzi usando Latin-1. Partendo dal presupposto che gli Appunti non alterano la stringa, il round trip dovrebbe funzionare.


4

La sequenza \ x .. è qualcosa di specifico per Python. Non è una sequenza di escape byte universale.

Il modo in cui si entra effettivamente in non ASCII con codifica UTF-8 dipende dal sistema operativo in uso e / o dall'editor. Ecco come lo fai in Windows . Affinché OS X entri in a con un accento acuto puoi semplicemente premere option+ E, quindi A, e quasi tutti gli editor di testo in OS X supportano UTF-8.


3

Puoi anche migliorare la open()funzione originale per lavorare con i file Unicode sostituendola in posizione, usando la partialfunzione. Il bello di questa soluzione è che non è necessario modificare alcun vecchio codice. È trasparente.

import codecs
import functools
open = functools.partial(codecs.open, encoding='utf-8')

1

Stavo cercando di analizzare iCal usando Python 2.7.9:

dal calendario di importazione di icalendar

Ma stavo ottenendo:

 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)

ed è stato risolto con solo:

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

(Ora può stampare come il báss.)


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.