2013-06-24 17 views
7

Sembra che la codifica UTF-8 di Python (pacchetto codecs) interpreti i caratteri Unicode 28, 29 e 30 come terminazioni di riga. Perché? E come posso impedirgli di farlo?Codec code Python che termina

codice Esempio:

with open('unicodetest.txt', 'w') as f: 
    f.write('a'+chr(28)+'b'+chr(29)+'c'+chr(30)+'d'+chr(31)+'e') 
with open('unicodetest.txt', 'r') as f: 
    for i,l in enumerate(f): 
    print i, l 
# prints "0 abcde" with special characters in between. 

Il punto qui è che si legge come una riga come mi aspetto di fare. Ora quando uso codecs per leggerlo in UTF-8, lo interpreta come molte linee.

import codecs 
with codecs.open('unicodetest.txt', 'r', 'UTF-8') as f: 
    for i,l in enumerate(f): 
    print i, l 
# 0 a 
# 1 b 
# 2 c 
# 3 de 
# (again with the special characters after each a, b, c, d 

I caratteri da 28 a 31 sono descritti come "Information Separator quattro" a "One" (in questo ordine). Due cose mi colpiscono: 1) 28 a 30 sono interpretate come estremità di linea, 2) 31 non lo è. È questo comportamento previsto? Dove posso trovare una definizione di quali caratteri sono interpretati come line-ends? C'è un modo per non interpretarli come line-line?

Grazie.

modifica dimenticato di copiare l'argomento 'UTF-8' in codecs.open. Il codice nella mia domanda è ora corretto.

+0

Cosa succede se apri il file in modalità '' rb''? – unutbu

+0

Non fa differenza. – Paul

+2

@Paul, puoi rispondere alla tua domanda e accettarla se ti piace –

risposta

5

Questa è una grande domanda.

Fa la differenza se si apre un file con open() o codecs.open(). Il primo funziona in termini di stringhe di byte. Quest'ultimo funziona in termini di stringhe Unicode. In Python, questi behave differently.

Questa stessa domanda si presentava come Python Issue 7643, What is a Unicode line break character?. La discussione e le citazioni allo Unicode Character Database, sono affascinanti. Edizione 7643 dà anche questo frammento di codice conciso per dimostrare la differenza:

for s in '\x0a\x0d\x1c\x1d\x1e': 
    print u'a{}b'.format(s).splitlines(1), 'a{}b'.format(s).splitlines(1) 

Ma tutto si riduce a questo.

Per determinare se i byte nelle stringhe di byte sono interruzioni di riga (o spazi bianchi), Python utilizza le regole di ASCII control characters. Con questa misura, i byte 10 e 13 sono caratteri di interruzione di riga (e Python considera il byte 13 seguito da 10 come interruzione di riga singola).

Ma per determinare se i caratteri in stringhe Unicode sono interruzioni di linea, Python segue le classificazioni dei caratteri del Unicode Character Database, documentati in UAX #44, e del UAX #14 Line Breaking Algorithm, section 5 Line Breaking Properties. Secondo Problema 7643, questi documenti individuano tre proprietà dei caratteri che identificano un personaggio come un'interruzione di linea a fini di Python:

  • generale Categoria Zl "linea di separazione"
  • generale Categoria Zp "Paragrafo separatore"
  • bidirezionale Classe B "Separatore di paragrafo"

I caratteri 28 (0x001C), 29 (0x001D) e 30 (0x001E) hanno quelle proprietà di carattere. Il carattere 31 (0x001F) no. Perché? Questa è una domanda per il Comitato tecnico Unicode. Ma in ASCII, questi caratteri erano noti come "Separatore di file", "Separatore di gruppo", "Separatore di record" e "Separatore di unità".Utilizzando un file di dati di testo a schede come un confronto, i primi tre connotano almeno la stessa separazione di un'interruzione di riga, mentre il quarto è forse analogo alla scheda.

È possibile visualizzare il codice che definisce effettivamente questi tre caratteri Unicode come interruzioni di riga nelle stringhe Python Unicode in Objects/unicodeobject.c. Cerca l'array ascii_linebreak[]. Questo array è alla base dell'implementazione di unicode.splitlines(). Codice diverso sottostanti str.splitlines(). Io credo, ma non l'ho rintracciato nel codice sorgente di Python, chesu un file aperto con codecs.open() è implementato in termini di unicode.splitlines().

Chiedi, "come posso impedirlo?" Non vedo alcun modo per rendere splitlines() un comportamento diverso. Tuttavia, è possibile aprire il file come un flusso di byte, leggere le linee come byte con il comportamento str.splitlines(), poi decodificare ogni riga come UTF-8 per l'uso come una stringa Unicode:

with open('unicodetest.txt', 'r') as f: 
    for i,l in enumerate(f): 
    print i, l.decode('UTF-8') 
# prints "0 abcde" with special characters in between. 

Presumo che si sta utilizzando Python 2 .x, non 3.x. La mia risposta è basata su Python 2.7.

+1

Grazie. Questo è elaborato. E grazie per la tua soluzione. Ha senso. – Paul