在Python 2.7中,当将一个unicode字符串传递给XML声明中具有fromstring()
的ElementTree encoding="UTF-16"
方法时,我得到一个ParseError,说明指定的编码是不正确的:
>>> from xml.etree import ElementTree
>>> data = u'<?xml version="1.0" encoding="utf-16"?><root/>'
>>> ElementTree.fromstring(data)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\Program Files (x86)\Python 2.7\lib\xml\etree\ElementTree.py", line 1300, in XML
parser.feed(text)
File "C:\Program Files (x86)\Python 2.7\lib\xml\etree\ElementTree.py", line 1642, in feed
self._raiseerror(v)
File "C:\Program Files (x86)\Python 2.7\lib\xml\etree\ElementTree.py", line 1506, in _raiseerror
raise err
xml.etree.ElementTree.ParseError: encoding specified in XML declaration is incorrect: line 1, column 30
这是什么意思?是什么让ElementTree这么认为?
毕竟,我传递的是unicode代码点,而不是字节串。这里没有涉及编码。怎么会不正确?
当然,有人可能会争辩说任何编码都是错误的,因为这些unicode代码点没有编码。然而,那么为什么UTF-8不被拒绝作为&#34;错误的编码&#34;?
>>> ElementTree.fromstring(u'<?xml version="1.0" encoding="utf-8"?><root/>')
我可以通过将unicode字符串编码为UTF-16编码的字节字符串并将其传递给fromstring()
或将encoding="utf-16"
替换为unicode中的encoding="utf-8"
来轻松解决此问题字符串,但我想了解为什么会引发异常。 documentation of ElementTree没有说明仅接受字节字符串。
具体来说,我想避免这些额外的操作,因为我的输入数据可能会变得很大,我想避免在内存中使用它们两次,并且处理它们的CPU开销超过绝对必要。
答案 0 :(得分:16)
我不会试图证明这种行为的合理性,而是要解释为什么它实际上是按照所写的代码发生的。
简而言之:Python使用的XML解析器expat对字节进行操作,而不是对unicode字符进行操作。在将字符串传递给.encode('utf-16-be')
之前,您必须在字符串上调用.encode('utf-16-le')
或ElementTree.fromstring
:
ElementTree.fromstring(data.encode('utf-16-be'))
证明:ElementTree.fromstring
最终调用pyexpat.xmlparser.Parse
,这是在pyexpat.c中实现的:
static PyObject *
xmlparse_Parse(xmlparseobject *self, PyObject *args)
{
char *s;
int slen;
int isFinal = 0;
if (!PyArg_ParseTuple(args, "s#|i:Parse", &s, &slen, &isFinal))
return NULL;
return get_parse_result(self, XML_Parse(self->itself, s, slen, isFinal));
}
因此,您传入的unicode参数将使用s#
进行转换。 PyArg_ParseTuple
的{{3}}说:
s#(字符串,Unicode或任何与读取缓冲区兼容的对象)[const char *,int(或Py_ssize_t,见下文)]这个变量在s上存储成两个C变量,第一个是指向字符串的指针,第二个是 一个长度。在这种情况下,Python字符串可能包含嵌入式 空字节。 Unicode对象传回指向默认编码的指针 如果可以进行此类转换,则可以使用对象的字符串版本。所有 其他读取缓冲区兼容的对象传回对raw的引用 内部数据表示。
让我们看一下:
from xml.etree import ElementTree
data = u'<?xml version="1.0" encoding="utf-8"?><root>\u2163</root>'
print ElementTree.fromstring(data)
给出错误:
UnicodeEncodeError: 'ascii' codec can't encode character u'\u2163' in position 44: ordinal not in range(128)
这意味着当您指定encoding="utf-8"
时,您很幸运,当Unicode字符串被编码为ASCII时,输入中没有非ASCII字符。如果在解析之前添加以下内容,则UTF-8将按预期工作:
import sys
reload(sys).setdefaultencoding('utf8')
但是,将默认编码设置为“utf-16-be&#39;”并不起作用。或者&#39; utf-16-le&#39;,因为ElementTree的Python位进行直接字符串比较,在UTF-16版本中开始失败。