Tipi di Python str vs unicode


101

Lavorando con Python 2.7, mi chiedo quale sia il vero vantaggio nell'usare il tipo unicodeinvece di str, poiché entrambi sembrano essere in grado di contenere stringhe Unicode. C'è qualche motivo speciale oltre alla possibilità di impostare i codici Unicode in unicodestringhe utilizzando il carattere di escape \?:

Eseguire un modulo con:

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

a = 'á'
ua = u'á'
print a, ua

Risultati in: á, á

MODIFICARE:

Ulteriori test utilizzando la shell Python:

>>> a = 'á'
>>> a
'\xc3\xa1'
>>> ua = u'á'
>>> ua
u'\xe1'
>>> ua.encode('utf8')
'\xc3\xa1'
>>> ua.encode('latin1')
'\xe1'
>>> ua
u'\xe1'

Quindi, la unicodestringa sembra essere codificata usando latin1invece di utf-8e la stringa non elaborata è codificata usando utf-8? Adesso sono ancora più confuso! :S


Non c'è codifica per unicode, è solo un'astrazione del carattere Unicode; unicodepuò essere convertito in strcon alcune codifiche (ad esempio utf-8).
Bin

Risposte:


178

unicodeha lo scopo di gestire il testo . Il testo è una sequenza di punti di codice che può essere più grande di un singolo byte . Il testo può essere codificato in una codifica specifica per rappresentare il testo come byte non elaborati (ad esempio utf-8, latin-1...).

Nota che unicode non è codificato ! La rappresentazione interna usata da python è un dettaglio di implementazione e non dovresti preoccupartene fintanto che è in grado di rappresentare i punti di codice che desideri.

Al contrario strin Python 2 è una semplice sequenza di byte . Non rappresenta testo!

Puoi pensare a unicodecome una rappresentazione generale di un testo, che può essere codificato in molti modi diversi in una sequenza di dati binari rappresentati tramite str.

Nota: in Python 3, è unicodestato rinominato stre c'è un nuovo bytestipo per una semplice sequenza di byte.

Alcune differenze che puoi vedere:

>>> len(u'à')  # a single code point
1
>>> len('à')   # by default utf-8 -> takes two bytes
2
>>> len(u'à'.encode('utf-8'))
2
>>> len(u'à'.encode('latin1'))  # in latin1 it takes one byte
1
>>> print u'à'.encode('utf-8')  # terminal encoding is utf-8
à
>>> print u'à'.encode('latin1') # it cannot understand the latin1 byte

Nota che usando strhai un controllo di livello inferiore sui singoli byte di una specifica rappresentazione di codifica, mentre usando unicodepuoi controllare solo a livello di punto di codice. Ad esempio puoi fare:

>>> 'àèìòù'
'\xc3\xa0\xc3\xa8\xc3\xac\xc3\xb2\xc3\xb9'
>>> print 'àèìòù'.replace('\xa8', '')
à�ìòù

Quello che prima era valido UTF-8, non lo è più. Utilizzando una stringa Unicode non è possibile operare in modo tale che la stringa risultante non sia testo Unicode valido. È possibile rimuovere un punto di codice, sostituire un punto di codice con un punto di codice diverso ecc. Ma non è possibile modificare la rappresentazione interna.


4
Grazie mille per la tua risposta, ha aiutato molto! La parte più chiarificatrice per me è: "unicode non è codificato! La rappresentazione interna usata da python è un dettaglio di implementazione, e non dovresti preoccupartene [...]". Quindi, quando si serializzano gli unicodeoggetti, immagino che dobbiamo prima esplicitarli encode()nel formato di codifica appropriato, poiché non sappiamo quale viene utilizzato internamente per rappresentare il unicodevalore.
Caumons

10
Sì. Quando vuoi salvare del testo (ad esempio in un file) devi rappresentarlo con byte, cioè devi codificarlo . Quando si recupera il contenuto è necessario conoscere la codifica utilizzata, in modo da poter decodificare i byte in un unicodeoggetto.
Bakuriu

Mi dispiace, ma l'affermazione che unicodenon è codificata è chiaramente sbagliata. Anche UTF-16 / UCS-2 e UTF-32 / UCS-4 sono codifiche ... e in futuro potrebbero essere create altre di queste. Il punto è che solo perché non dovresti preoccuparti dei dettagli di implementazione (e, in effetti, non dovresti!), Ancora non significa che unicodenon sia codificato. Lo è, ovviamente. Se può essere .decode()'d è tutta un'altra storia.
0xC0000022L

1
@ 0xC0000022L Forse la frase così com'è non è chiara. Dovrebbe dire: la unicoderappresentazione interna dell'oggetto può essere qualunque cosa desideri, inclusa una non standard. In particolare in python3 + unicode fa uso di una rappresentazione interna non standard che cambia anche a seconda dei dati contenuti. In quanto tale non è una codifica standard . Unicode come standard di testo definisce solo codepoints che sono una rappresentazione astratta del testo, ci sono un sacco di modi per codificare Unicode in memoria, incluso lo standard utf-X, ecc. Python usa il proprio modo per l'efficienza.
Bakuriu,

1
@ 0xC0000022L Anche il fatto che UTF-16 sia una codifica non ha nulla a che fare con l' unicodeoggetto CPython , poiché non utilizza UTF-16, né UTF-32. Usa una rappresentazione ad hoc e se vuoi codificare i dati in byte effettivi devi usare encode. Inoltre: il linguaggio non impone come unicodeviene implementato, quindi diverse versioni o implementazioni di python possono (e hanno ) diverse rappresentazioni interne.
Bakuriu

38

Unicode e le codifiche sono cose completamente diverse e non correlate.

Unicode

Assegna un ID numerico a ogni carattere:

  • 0x41 → A
  • 0xE1 → á
  • 0x414 → Д

Quindi, Unicode assegna il numero 0x41 ad A, 0xE1 a á e 0x414 a Д.

Anche la piccola freccia → che ho usato ha il suo numero Unicode, è 0x2192. E anche gli emoji hanno i loro numeri Unicode, 😂 è 0x1F602.

Puoi cercare i numeri Unicode di tutti i caratteri in questa tabella . In particolare, puoi trovare i primi tre caratteri qui sopra , la freccia qui e l'emoji qui .

Questi numeri assegnati a tutti i caratteri da Unicode sono chiamati punti di codice .

Lo scopo di tutto ciò è fornire un mezzo per fare riferimento in modo inequivocabile a ciascun personaggio. Ad esempio, se sto parlando di 😂, invece di dire "sai, questa emoji che ride con le lacrime" , posso semplicemente dire, punto di codice Unicode 0x1F602 . Più facile, vero?

Tieni presente che i punti di codice Unicode sono generalmente formattati con un'interlinea U+, quindi il valore numerico esadecimale viene riempito di almeno 4 cifre. Quindi, gli esempi precedenti sarebbero U + 0041, U + 00E1, U + 0414, U + 2192, U + 1F602.

I punti del codice Unicode vanno da U + 0000 a U + 10FFFF. Questo è 1.114.112 numeri. 2048 di questi numeri vengono utilizzati per i surrogati , quindi rimangono 1.112.064. Ciò significa che Unicode può assegnare un ID univoco (punto di codice) a 1.112.064 caratteri distinti. Non tutti questi punti di codice sono ancora assegnati a un carattere e Unicode viene esteso continuamente (ad esempio, quando vengono introdotti nuovi emoji).

La cosa importante da ricordare è che tutto ciò che Unicode fa è assegnare un ID numerico, chiamato punto di codice, a ciascun carattere per un riferimento facile e univoco.

Codifiche

Mappare i caratteri su schemi di bit.

Questi modelli di bit vengono utilizzati per rappresentare i caratteri nella memoria del computer o su disco.

Esistono molte codifiche diverse che coprono diversi sottoinsiemi di caratteri. Nel mondo anglofono, le codifiche più comuni sono le seguenti:

ASCII

Mappa 128 caratteri (punti di codice da U + 0000 a U + 007F) a schemi di bit di lunghezza 7.

Esempio:

  • a → 1100001 (0x61)

Puoi vedere tutte le mappature in questa tabella .

ISO 8859-1 (noto anche come Latin-1)

Mappa 191 caratteri (punti di codice da U + 0020 a U + 007E e da U + 00A0 a U + 00FF) a schemi di bit di lunghezza 8.

Esempio:

  • a → 01100001 (0x61)
  • á → 11100001 (0xE1)

Puoi vedere tutte le mappature in questa tabella .

UTF-8

Mappe 1,112,064 caratteri (tutti i punti codice Unicode esistenti) ai modelli di bit di ciascuna lunghezza 8, 16, 24, o 32 bit (cioè, 1, 2, 3, o 4 byte).

Esempio:

  • a → 01100001 (0x61)
  • á → 11000011 10100001 (0xC3 0xA1)
  • ≠ → 11100010 10001001 10100000 (0xE2 0x89 0xA0)
  • 😂 → 11110000 10011111 10011000 10000010 (0xF0 0x9F 0x98 0x82)

Il modo in cui UTF-8 codifica i caratteri in stringhe di bit è descritto molto bene qui .

Unicode e codifiche

Guardando gli esempi precedenti, diventa chiaro quanto sia utile Unicode.

Ad esempio, se sono Latin-1 e voglio spiegare la mia codifica di á, non ho bisogno di dire:

"Codifico che a con un aigu (o comunque tu chiami quella barra crescente) come 11100001"

Ma posso solo dire:

"Codifico U + 00E1 come 11100001"

E se sono UTF-8 , posso dire:

"Io, a mia volta, codifico U + 00E1 come 11000011 10100001"

Ed è inequivocabilmente chiaro a tutti quale personaggio intendiamo.

Ora alla confusione che spesso sorge

È vero che a volte lo schema di bit di una codifica, se lo interpreti come un numero binario, è lo stesso del punto di codice Unicode di questo carattere.

Per esempio:

  • ASCII codifica a come 1100001, che puoi interpretare come il numero esadecimale 0x61 e il punto di codice Unicode di a è U + 0061 .
  • Latin-1 codifica á come 11100001, che puoi interpretare come il numero esadecimale 0xE1 , e il punto di codice Unicode di á è U + 00E1 .

Naturalmente, questo è stato organizzato in questo modo apposta per comodità. Ma dovresti considerarla una pura coincidenza . Lo schema di bit utilizzato per rappresentare un carattere in memoria non è legato in alcun modo al punto di codice Unicode di questo carattere.

Nessuno dice nemmeno che devi interpretare una stringa di bit come 11100001 come numero binario. Considerala come la sequenza di bit che Latin-1 usa per codificare il carattere á .

Torna alla tua domanda

La codifica usata dal tuo interprete Python è UTF-8 .

Ecco cosa sta succedendo nei tuoi esempi:

Esempio 1

Quanto segue codifica il carattere á in UTF-8. Ciò risulta nella stringa di bit 11000011 10100001, che viene salvata nella variabile a.

>>> a = 'á'

Quando guardi il valore di a, il suo contenuto 11000011 10100001 è formattato come numero esadecimale 0xC3 0xA1 e restituito come '\xc3\xa1':

>>> a
'\xc3\xa1'

Esempio 2

Quanto segue salva il punto di codice Unicode di á, che è U + 00E1, nella variabile ua(non sappiamo quale formato dati Python utilizza internamente per rappresentare il punto di codice U + 00E1 in memoria, e per noi non è importante):

>>> ua = u'á'

Quando guardi il valore di ua, Python ti dice che contiene il punto di codice U + 00E1:

>>> ua
u'\xe1'

Esempio 3

Quanto segue codifica il punto di codice Unicode U + 00E1 (che rappresenta il carattere á) con UTF-8, che risulta nella sequenza di bit 11000011 10100001. Di nuovo, per l'output questa sequenza di bit è rappresentata come numero esadecimale 0xC3 0xA1:

>>> ua.encode('utf-8')
'\xc3\xa1'

Esempio 4

Quanto segue codifica il punto di codice Unicode U + 00E1 (che rappresenta il carattere á) con Latin-1, che risulta nel modello di bit 11100001. Per l'output, questo modello di bit è rappresentato come il numero esadecimale 0xE1, che per coincidenza è lo stesso dell'iniziale punto di codice U + 00E1:

>>> ua.encode('latin1')
'\xe1'

Non esiste alcuna relazione tra l'oggetto Unicode uae la codifica Latin-1. Che il punto di codice di á è U + 00E1 e la codifica Latin-1 di á è 0xE1 (se si interpreta il modello di bit della codifica come un numero binario) è una pura coincidenza.


31

Il tuo terminale è configurato per UTF-8.

Il fatto che la stamperia asia una coincidenza; stai scrivendo byte UTF-8 non elaborati sul terminale. aè un valore di lunghezza due , contenente due byte, valori esadecimali C3 e A1, mentre uaè un valore Unicode di lunghezza uno , contenente un codepoint U + 00E1.

Questa differenza di lunghezza è uno dei motivi principali per utilizzare i valori Unicode; non è possibile misurare facilmente il numero di caratteri di testo in una stringa di byte; il len()di una stringa di byte ti dice quanti byte sono stati usati, non quanti caratteri sono stati codificati.

Puoi vedere la differenza quando codifichi il valore Unicode in diverse codifiche di output:

>>> a = 'á'
>>> ua = u'á'
>>> ua.encode('utf8')
'\xc3\xa1'
>>> ua.encode('latin1')
'\xe1'
>>> a
'\xc3\xa1'

Si noti che i primi 256 codepoint dello standard Unicode corrispondono allo standard Latin 1, quindi il codepoint U + 00E1 è codificato in Latin 1 come byte con valore esadecimale E1.

Inoltre, Python utilizza codici di escape nelle rappresentazioni di stringhe di byte e unicode allo stesso modo, e anche i punti di codice bassi che non sono ASCII stampabili sono rappresentati utilizzando \x..valori di escape. Questo è il motivo per cui una stringa Unicode con un punto di codice tra 128 e 255 sembra proprio come la 1 codifica latino. Se si dispone di una stringa Unicode con punti di codice oltre U + 00FF, \u....viene utilizzata invece una sequenza di escape diversa , con un valore esadecimale a quattro cifre.

Sembra che tu non abbia ancora compreso appieno qual è la differenza tra Unicode e una codifica. Si prega di leggere i seguenti articoli prima di continuare:


Ho modificato la mia domanda con ulteriori test. Ho letto per unicode e le diverse codifiche per un po 'e penso di aver capito la teoria, ma quando provo effettivamente il codice Python non capisco cosa sta succedendo
Caumons

1
La codifica latin-1 corrisponde ai primi 256 punti di codice dello standard Unicode. Questo è il motivo per cui U + 00E1 codifica \xe1in latino 1.
Martijn Pieters

2
Questo è l'aspetto più importante di Unicode. Non è una codifica . È testo. Unicode è uno standard che include molto, molto di più, come le informazioni su quali codepoint sono numeri, o spazi bianchi o altre categorie, dovrebbero essere visualizzati da sinistra a destra o da destra a sinistra, ecc. Ecc. Ecc.
Martijn Pieters

1
È come dire che Unicode è come una "interfaccia" e la codifica è come una vera e propria "implementazione".
Caumons

2
@ Varun: devi usare una build stretta di Python 2, che utilizza internamente UCS-2 e travisa qualsiasi cosa oltre U + FFFF come di lunghezza due. Python 3 e una build UCS-2 (wide) ti mostreranno che la lunghezza è davvero 1.
Martijn Pieters

2

Quando si definisce a come unicode, i caratteri a e á sono uguali. Altrimenti á conta come due caratteri. Prova len (a) e len (au). Inoltre, potrebbe essere necessario disporre della codifica quando si lavora con altri ambienti. Ad esempio, se usi md5, ottieni valori diversi per a e ua

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.