异常消息的默认编码

时间:2009-09-02 17:27:17

标签: python exception encoding python-2.x

以下代码检查了在输入非ascii符号时float()方法的行为:

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 个答案:

答案 0 :(得分:8)

e [0]不用latin-1编码;事实上,当解码为latin-1时,字节\ xbd是字符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是一个字节字符串,因此它只是直接包含它。

答案 1 :(得分:5)

非常好的问题!

我冒昧地深入研究Python的源代码,这只是在正确设置Linux发行版时的命令(apt-get source python2.5

该死的,John Millikin打败了我。没错,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;
    }

看,它留下了所有unicode代码点&lt;基于Unicode的向后兼容性,256个就位,即latin-1个字符。


附录

有了这个,你可以通过尝试其他非拉丁-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

答案 2 :(得分:2)

ASCII编码仅包含值为<= 127的字节。这些字节表示的字符范围在大多数编码中是相同的;换句话说,“A”在ASCII中为chr(65),在latin-1中为UTF-8,依此类推。

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

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

float(u'\xbd')会导致floatobject.c中的PyFloat_FromString被调用。这个函数给出了一个unicode对象,然后调用unicodeobject.c中被调用的PyUnicode_EncodeDecimal。通过略过代码,我得到了这个函数将unicode对象转换成字符串,方法是用一个unicode代码点<256替换每个字符,该字符包含该值的字节,即具有代码点189的一半字符,变为chr(89)

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

此异常的参数是字符串

"invalid literal for float(): " + evil_string

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

答案 3 :(得分:0)

通过试验您的代码片段,我的平台上看起来会有相同的行为(OS X 10.5上的Py2.6)。

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

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