2009-09-02 10 views
6

Các mã sau xem xét các hành vi của các phương pháp float() khi ăn một biểu tượng phi ascii:Mã hóa mặc định các thông điệp ngoại lệ

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)" 

Câu hỏi của tôi: tại sao các thông báo lỗi e[0] mã hóa trong Latin-1? Mã hóa mặc định là Ascii, và điều này có vẻ là những gì unicode() mong đợi.

Platform là Ubuntu 9,04, Python 2.6.2

Trả lời

8

e [0] không được mã hóa bằng latin-1; nó chỉ xảy ra khi byte \ xbd, khi được giải mã là latin-1, là ký tự U + 00BD.

Chuyển đổi xảy ra trong Objects/floatobject.c.

Đầu tiên, chuỗi unicode phải được chuyển đổi thành chuỗi byte. Điều này được thực hiện bằng cách sử dụng PyUnicode_EncodeDecimal():

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

được triển khai trong unicodeobject.c. Nó không thực hiện bất kỳ loại chuyển đổi bộ ký tự nào, nó chỉ ghi byte với các giá trị bằng các số thứ tự unicode của chuỗi. Trong trường hợp này, U + 00BD -> 0xBD.

Tuyên bố định dạng lỗi là:

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

nơi s chứa chuỗi byte đã tạo trước đó. PyOS_snprintf() ghi một chuỗi byte và s là một chuỗi byte, do đó, nó chỉ bao gồm nó một cách trực tiếp.

+0

Điều này có nên được coi là lỗi trong Python không? Lý do của tôi: nếu float() nhận được một chuỗi Unicode, nó sẽ ném một ngoại lệ được mô tả bằng Unicode nếu thông báo sẽ bao gồm đầu vào. Nếu không, các ngoại lệ không thể được xử lý an toàn, như ví dụ cho thấy. – pablobm

+3

Tôi nghĩ rằng gọi nó là một lỗi là công bằng - các messaeg lỗi nên có lẽ chứa 'repr (v)' thay vì 'str (s)', như biết giá trị đầu vào ban đầu là hữu ích hơn so với phiên bản mã hóa thập phân. –

2

Các mã hóa ASCII chỉ bao gồm các byte với giá trị <= 127. Phạm vi của các ký tự đại diện bởi các byte này giống hệt nhau trong hầu hết các mã hóa; nói cách khác, "A" là chr(65) bằng ASCII, bằng latin-1, UTF-8, v.v.

Ký hiệu một nửa, không phải là một phần của bộ ký tự ASCII, vì vậy khi Python cố gắng mã hóa biểu tượng này thành ASCII, nó không thể làm gì ngoài thất bại.

Cập nhật: Dưới đây là những gì xảy ra (tôi giả sử chúng ta đang nói CPython):

float(u'\xbd') dẫn đến PyFloat_FromString trong floatobject.c được gọi. Hàm này, cho đối tượng unicode, lần lượt gọi PyUnicode_EncodeDecimal trong số unicodeobject.c đang được gọi. Từ skimming qua mã, tôi nhận được nó rằng chức năng này biến đối tượng unicode thành một chuỗi bằng cách thay thế mỗi ký tự bằng một mã số unicode <256 với byte của giá trị đó, tức là một nửa ký tự, có codepoint 189, được chuyển thành chr(89) .

Sau đó, PyFloat_FromString hoạt động như bình thường. Tại thời điểm này, nó làm việc với một chuỗi thông thường, mà xảy ra có chứa một byte byte không phải ASCII. Nó không quan tâm về điều này; nó chỉ tìm thấy một byte mà không phải là một chữ số, một khoảng thời gian hoặc tương tự, do đó, nó làm tăng lỗi giá trị.

Đối số cho ngoại lệ này là một chuỗi

"invalid literal for float(): " + evil_string 

Đó là tốt; một thông báo ngoại lệ, sau khi tất cả, một chuỗi. Chỉ khi bạn cố gắng giải mã chuỗi này, sử dụng ASCII mã hóa mặc định, điều này biến thành một vấn đề.

+0

Đó không trả lời câu hỏi của tôi , Tôi sợ. Tôi đã chỉnh sửa một chút văn bản gốc để rõ ràng hơn. (Tôi đã thay đổi câu hỏi bằng chữ in đậm). Tôi hiểu Ascii không thể cung cấp đại diện cho nhân vật lạ. Vấn đề của tôi là e [0] dường như được mã hóa bằng tiếng Latin-1, mặc dù Ascii là mã hóa mặc định. Lý do của tôi là float() đáng lẽ phải nêu ra một ngoại lệ được mã hóa Ascii (hoặc Unicode). Tuy nhiên, nó bằng tiếng Latin-1 hoặc một cái gì đó tương tự thay thế. Nó đã cố gắng mã hóa thông báo lỗi trong Ascii và thất bại, nâng một UnicodeDecodeError ở nơi đầu tiên, không phải là một ValueError. – pablobm

+0

OK, tôi đã cập nhật câu trả lời. – balpha

0

Từ thử nghiệm với đoạn mã của bạn, có vẻ như tôi có cùng hành vi trên nền tảng của mình (Py2.6 trên OS X 10.5).

Vì bạn thiết lập mà e [0] được mã hóa với latin-1, cách chính xác để chuyển đổi nó unicode là để làm .decode('latin-1'), và khôngunicode(e[0]).

Cập nhật: Vì vậy, có vẻ như e [0] không có mã hóa hợp lệ. Definetely không latin-1. Do đó, như đã đề cập ở đâu đó trong các ý kiến, bạn sẽ phải gọi repr(e[0]) nếu bạn cần hiển thị thông báo lỗi này với một ngoại lệ xếp tầng.

+1

Nó không mã hóa như latin-1, nó chỉ chuyển đổi giá trị thứ tự unicode thành byte. –

+0

Đã cập nhật, cảm ơn. –

5

Câu hỏi rất hay!

tôi lấy sự tự do để thâm nhập vào mã nguồn Python, mà là chỉ một lệnh lập tức trên được thiết lập đúng các bản phân phối Linux (apt-get source python2.5)

Chết tiệt, John Millikin đánh tôi với nó. Đúng vậy, PyUnicode_EncodeDecimal là câu trả lời nó thực hiện điều này ở đây:

/* (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; 
    } 

Xem, nó để lại tất cả các điểm mã unicode < 256 tại chỗ, đó là những latin-1 ký tự, dựa trên khả năng tương thích ngược Unicode của.


Phụ Lục

Với điều này tại chỗ, bạn có thể xác minh bằng cách cố gắng phi latin-1 nhân vật khác, nó sẽ ném một ngoại lệ khác nhau:

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