6

以下代码检查了float()输入非 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)"

我的问题:为什么错误信息e[0]用 Latin-1 编码?默认编码是 Ascii,这似乎是unicode()预期的。

平台是 Ubuntu 9.04,Python 2.6.2

4

4 回答 4

9

e[0] 不是用 latin-1 编码的;恰好字节 \xbd 在解码为 latin-1 时是字符 U+00BD。

转换发生在Objects/floatobject.c.

首先,必须将 unicode 字符串转换为字节字符串。这是使用执行的PyUnicode_EncodeDecimal()

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

这是在unicodeobject.c. 它不执行任何类型的字符集转换,它只是写入值等于字符串的 unicode 序数的字节。在这种情况下,U+00BD -> 0xBD。

格式化错误的语句是:

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

其中s包含之前创建的字节字符串。PyOS_snprintf()写入一个字节串,并且s是一个字节串,所以它只是直接包含它。

于 2009-09-02T18:19:54.883 回答
5

非常好的问题!

我冒昧地深入研究了 Python 的源代码,这仅仅是一个命令就可以正确设置 linux 发行版(apt-get source python2.5

该死的,约翰米利金打败了我。没错,这PyUnicode_EncodeDecimal是它在这里做的答案:

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

看,它保留了所有 < 256 的 unicode 代码点,它们是 latin-1 字符,基于 Unicode 的向后兼容性。


附录

有了这个,你可以通过尝试其他非 latin-1 字符来验证,它会抛出一个不同的异常:

>>> 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
于 2009-09-02T18:23:23.223 回答
2

ASCII 编码只包括带有值的字节<= 127。这些字节表示的字符范围在大多数编码中是相同的;换句话说,“A”是chr(65)ASCII、latin-1、UTF-8 等等。

然而,半符号不是 ASCII 字符集的一部分,因此当 Python 尝试将此符号编码为 ASCII 时,它只能失败。

更新:这是发生的事情(我假设我们正在谈论 CPython):

float(u'\xbd')导致PyFloat_FromStringfloatobject.c中被调用。这个函数,给出一个 unicode 对象,依次调用被调用PyUnicode_EncodeDecimalunicodeobject.c。通过浏览代码,我知道该函数通过将每个字符替换为具有该值的字节的 unicode 代码点来将 unicode 对象转换为字符串<256,即具有代码点 189 的半字符被转换为chr(89).

然后,PyFloat_FromString照常工作。此时,它正在使用一个常规字符串,该字符串恰好包含一个非 ASCII 范围字节。它不关心这个;它只是找到一个不是数字、句点等的字节,所以它会引发值错误。

此异常的参数是一个字符串

"invalid literal for float(): " + evil_string

没关系; 毕竟,异常消息是一个字符串。只有当您尝试使用默认编码 ASCII 解码此字符串时,才会出现问题。

于 2009-09-02T17:49:23.883 回答
0

通过对您的代码片段进行试验,我的平台上似乎有相同的行为(OS X 10.5 上的 Py2.6)。

由于您确定 e[0] 是用 编码的latin-1,因此转换它的正确方法unicode是 do .decode('latin-1'),而不是 unicode(e[0])

更新:所以听起来 e[0] 没有有效的编码。绝对不是latin-1。因此,正如评论中其他地方所提到的,repr(e[0])如果您需要显示此错误消息而不会导致级联异常,则必须调用。

于 2009-09-02T18:04:17.723 回答