以下代码检查了在输入非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
答案 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
。