2009-09-02 15 views
6

Il seguente codice esamina il comportamento del metodo float() se somministrate un simbolo non-ascii:default codifica dei messaggi di eccezione

import sys 

try: 
    float(u'\xbd') 
except ValueError as e: 
    print sys.getdefaultencoding() # in my system, this is 'ascii' 
    print e[0].decode('latin-1') # u'invalid literal for float(): ' followed by the 1/2 (one half) character 
    print unicode(e[0]) # raises "UnicodeDecodeError: 'ascii' codec can't decode byte 0xbd in position 29: ordinal not in range(128)" 

La mia domanda: perché è il messaggio di errore e[0] codificato in Latin-1? La codifica predefinita è Ascii, e questo sembra essere quello che si aspetta unicode().

Platform è versione 9.04, Python 2.6.2

risposta

8

e [0] non è codificato con latin-1; si dà il caso che il byte \ xbd, quando decodificato come latin-1, sia il carattere U + 00BD.

La conversione avviene in Objects/floatobject.c.

Prima di tutto, la stringa unicode deve essere convertita in una stringa di byte. Questa operazione viene eseguita utilizzando PyUnicode_EncodeDecimal():

if (PyUnicode_EncodeDecimal(PyUnicode_AS_UNICODE(v), 
          PyUnicode_GET_SIZE(v), 
          s_buffer, 
          NULL)) 
     return NULL; 

che viene realizzato in unicodeobject.c. Non esegue alcun tipo di conversione di set di caratteri, scrive solo byte con valori uguali agli ordinali Unicode della stringa. In questo caso, U + 00BD -> 0xBD.

La dichiarazione di formattare l'errore è:

PyOS_snprintf(buffer, sizeof(buffer), 
       "invalid literal for float(): %.200s", s); 

dove s contiene la stringa di byte creato in precedenza. PyOS_snprintf() scrive una stringa di byte e s è una stringa di byte, quindi include solo la stringa direttamente.

+0

Questo dovrebbe essere considerato un bug in Python? Il mio ragionamento: se float() ha ricevuto una stringa Unicode, dovrebbe generare un'eccezione descritta in Unicode se il messaggio includerà l'input. Altrimenti, le eccezioni non possono essere gestite in sicurezza, come mostra l'esempio. – pablobm

+3

Penso che chiamare un bug sia corretto - l'errore messaggioeg probabilmente dovrebbe contenere 'repr (v)' invece di 'str (s)', poiché conoscere il valore di input originale è più utile della versione con codifica decimale. –

2

La codifica ASCII include solo i byte con valori <= 127. L'intervallo di caratteri rappresentati da questi byte è identico nella maggior parte delle codifiche; in altre parole, "A" è chr(65) in ASCII, in latin-1, in UTF-8 e così via.

Il simbolo a metà, tuttavia, non fa parte del set di caratteri ASCII, quindi quando Python prova a codificare questo simbolo in ASCII, non può fare altro che fallire.

Aggiornamento: Ecco cosa succede (presumo stiamo parlando CPython):

float(u'\xbd') porta a PyFloat_FromString in floatobject.c essere chiamato. Questa funzione, dando un oggetto unicode, a sua volta chiama lo PyUnicode_EncodeDecimal in unicodeobject.c. Da sfogliare il codice, capisco che questa funzione trasforma l'oggetto Unicode in una stringa sostituendo ogni carattere con un punto di codice unicode <256 con il byte di quel valore, cioè il carattere a metà, con il punto di codice 189, viene trasformato in chr(89) .

Quindi, PyFloat_FromString funziona come al solito. In questo momento sta funzionando con una stringa normale, che sembra contenere un byte di intervallo non ASCII. Non gli importa di questo; trova solo un byte che non è una cifra, un punto o simili, quindi solleva l'errore di valore.

L'argomento di questa eccezione è una stringa

"invalid literal for float(): " + evil_string 

va bene; un messaggio di eccezione è, dopo tutto, una stringa. È solo quando si tenta di decodificare questa stringa, utilizzando la codifica ASCII predefinita, che questo si trasforma in un problema.

+0

Questo non risponde alla mia domanda , Ho paura. Ho leggermente modificato il testo originale per essere più chiaro. (Ho cambiato la domanda in grassetto). Capisco che Ascii non possa offrire una rappresentazione per lo strano personaggio. Il mio problema è che e [0] sembra essere codificato in Latin-1, anche se Ascii è la codifica predefinita. Il mio ragionamento è che float() dovrebbe aver sollevato un'eccezione codificata in Ascii (o Unicode). Tuttavia, è in Latin-1 o qualcosa di simile invece. Dovrebbe aver provato a codificare il messaggio di errore in Ascii e fallito, sollevando un UnicodeDecodeError in primo luogo, non un errore ValueError. – pablobm

+0

Ok, ho aggiornato la risposta. – balpha

0

Dalla sperimentazione con lo snippet di codice, sembra che abbia lo stesso comportamento sulla mia piattaforma (Py2.6 su OS X 10.5).

Dal momento che avete stabilito che l'e [0] è codificato con latin-1, il modo corretto per convertirlo unicode è quello di fare .decode('latin-1'), e non unicode(e[0]).

Aggiornamento: Quindi sembra che e [0] non abbia una codifica valida. Assolutamente no latin-1. Per questo motivo, come menzionato altrove nei commenti, dovrai chiamare repr(e[0]) se devi visualizzare questo messaggio di errore senza generare un'eccezione a cascata.

+1

Non sta codificando come latin-1, sta solo convertendo i valori ordinali unicode in byte. –

+0

Aggiornato, grazie. –

5

Ottima domanda!

mi sono permesso di scavare nel codice sorgente di Python, che è un semplice comando di via sul impostato correttamente distribuzioni Linux (apt-get source python2.5)

Maledetti, John Millikin mi ha battuto ad esso. Proprio così, PyUnicode_EncodeDecimal è la risposta che fa questo qui:

/* (Loop ch in the unicode string) */ 
    if (Py_UNICODE_ISSPACE(ch)) { 
     *output++ = ' '; 
     ++p; 
     continue; 
    } 
    decimal = Py_UNICODE_TODECIMAL(ch); 
    if (decimal >= 0) { 
     *output++ = '0' + decimal; 
     ++p; 
     continue; 
    } 
    if (0 < ch && ch < 256) { 
     *output++ = (char)ch; 
     ++p; 
     continue; 
    } 
    /* All other characters are considered unencodable */ 
    collstart = p; 
    collend = p+1; 
    while (collend < end) { 
     if ((0 < *collend && *collend < 256) || 
      !Py_UNICODE_ISSPACE(*collend) || 
      Py_UNICODE_TODECIMAL(*collend)) 
      break; 
    } 

See, lascia tutti i punti di codice unicode < 256 sul posto, che sono i caratteri Latin-1, sulla base di compatibilità a ritroso di Unicode.


Addendum

Con questo in luogo, è possibile verificare provando altri non-Latin-1 caratteri, verrà un'eccezione diversa:

>>> float(u"ħ") 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
UnicodeEncodeError: 'decimal' codec can't encode character u'\u0127' in position 0: invalid decimal Unicode string 
Problemi correlati