Domanda

Il codice seguente esamina il comportamento del metodo float() se somministrate un simbolo non ASCII:

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 e[0] messaggio di errore codificato in Latin-1 La codifica predefinita è ASCII, e questo sembra essere quello che si aspetta unicode()

?.

Platform è Ubuntu 9.04, Python 2.6.2

È stato utile?

Soluzione

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

La conversione avviene in Objects/floatobject.c.

In primo luogo, la stringa unicode deve essere convertito 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 insieme di caratteri, scrive solo byte con valori pari a ordinali Unicode della stringa. In questo caso, U + 00BD -.> 0xBD

La dichiarazione 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, in modo che solo include direttamente.

Altri suggerimenti

Molto bella 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)

Accidenti , 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;
    }

Guarda, 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

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

Il simbolo una metà, però, non fa parte del set di caratteri ASCII, in modo che quando Python cerca di codificare questo simbolo in ASCII, si 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 PyUnicode_EncodeDecimal in unicodeobject.c essere chiamato. Dalla sfiorando il codice, ho capito che questa funzione trasforma l'oggetto unicode in una stringa sostituendo ogni carattere con un <256 unicode codepoint con il byte di quel valore, cioè la metà carattere, avente il valore di codice 189, si trasforma in chr(89) .

Poi, PyFloat_FromString fa il suo lavoro come al solito. In questo momento, sta funzionando con una stringa regolare, che sembra essere contenente un intervallo non ASCII byte. Essa non si preoccupa di questo; appena trova un byte che non è una cifra, un periodo o simili, in modo che genera l'errore di valore.

L'argomento di questa eccezione è una stringa

"invalid literal for float(): " + evil_string

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

Da sperimentare con te frammento di codice, sembrerebbe che ho lo stesso comportamento sulla mia piattaforma (Py2.6 su OS X 10.5).

Dal momento che si 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: Così suona come e [0] non ha una codifica valida. Sicuramente non latin-1. A causa di ciò, come accennato altrove nei commenti, si dovrà chiamare repr(e[0]) se è necessario per visualizzare questo messaggio di errore w / o causando un'eccezione cascata.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top