字符串和Unicode处理

一般来说,libcurl不执行数据编码或解码。特别是,libcurl不支持Unicode,而是对字节流进行操作。libcurl将其留给应用程序pycurl库或在本例中使用pycurl的应用程序,以便将Unicode数据编码和解码为字节流。

pycurl是libcurl的一个薄包装器,通常也不执行这种编码和解码,这取决于应用程序。明确地:

  • pycurl通过回调函数传递给应用程序的数据通常是字节字符串。应用程序必须对它们进行解码以获取文本(Unicode)数据。

  • 应用程序传递给pycurl的数据,如via setopt 调用,通常必须是适当编码的字节字符串。为了方便和与现有代码兼容,pycurl将只接受包含ASCII码位的Unicode字符串。 [1], 并透明地将它们编码为字节字符串。

为什么pycurl不自动编码和解码,比如HTTP请求或响应数据?要记住的关键是libcurl支持20多个协议,而pycurl通常不知道特定请求正在使用什么协议,因为pycurl不跟踪应用程序状态。不幸的是,人工编码和解码数据是libcurl灵活性的代价。

设置选项-python 2.x

在python 2下, str 类型可以保存任意编码的字节字符串。pycurl将传递给libcurl的任何字节字符串。以下代码将起作用:

>>> import pycurl
>>> c = pycurl.Curl()
>>> c.setopt(c.USERAGENT, 'Foo\xa9')
# ok

可以使用Unicode字符串,但必须仅包含ASCII码位::

>>> c.setopt(c.USERAGENT, u'Foo')
# ok

>>> c.setopt(c.USERAGENT, u'Foo\xa9')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character u'\xa9' in position 3: ordinal not in range(128)

>>> c.setopt(c.USERAGENT, u'Foo\xa9'.encode('iso-8859-1'))
# ok

设置选项-python 3.x

在python 3下, bytes 类型保存任意编码的字节字符串。PycURL 将接受 bytes libcurl指定“string”参数的所有选项的值:

>>> import pycurl
>>> c = pycurl.Curl()
>>> c.setopt(c.USERAGENT, b'Foo\xa9')
# ok

这个 str 类型保存Unicode数据。PycURL 将接受 str 仅包含ASCII码位的值::

>>> c.setopt(c.USERAGENT, 'Foo')
# ok

>>> c.setopt(c.USERAGENT, 'Foo\xa9')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeEncodeError: 'ascii' codec can't encode character '\xa9' in position 3: ordinal not in range(128)

>>> c.setopt(c.USERAGENT, 'Foo\xa9'.encode('iso-8859-1'))
# ok

正在写入文件

pycurl将以字节字符串的形式返回从网络中读取的所有数据。在python 2上,这意味着写回调将收到 str 对象,在python 3上,写回调将收到 bytes 物体。

在python 2下使用时,例如 WRITEDATAWRITEFUNCTION 选项,正在写入的文件 应该 以二进制模式打开。写入以文本模式打开的文件不会引发异常,但可能会损坏数据。

在python 3下,pycurl使用 bytes 实例。写入文件时,必须以二进制模式打开文件才能使写入工作:

import pycurl
c = pycurl.Curl()
c.setopt(c.URL,'http://pycurl.io')
# File opened in binary mode.
with open('/dev/null','wb') as f:
    c.setopt(c.WRITEDATA, f)
    # Same result if using WRITEFUNCTION instead:
    #c.setopt(c.WRITEFUNCTION, f.write)
    c.perform()
# ok

如果以文本模式打开文件 (w 而不是 wb 模式),将导致类似以下错误:

TypeError: must be str, not bytes
Traceback (most recent call last):
  File "/tmp/test.py", line 8, in <module>
    c.perform()
pycurl.error: (23, 'Failed writing body (0 != 168)')

typeerror实际上是pycurl所引发的一个异常,它将由pycurl打印,但不会传播。Pycrl将提高 pycurl.error 表示操作失败。

写入stringio/bytesio

在python 2下,可以使用 StringIO 对象:

import pycurl
from StringIO import StringIO
c = pycurl.Curl()
c.setopt(c.URL,'http://pycurl.io')
buffer = StringIO()
c.setopt(c.WRITEDATA, buffer)
# Same result if using WRITEFUNCTION instead:
#c.setopt(c.WRITEFUNCTION, buffer.write)
c.perform()
# ok

在python 3下,当pycurl使用 bytes 参数,响应必须写入 BytesIO 对象:

import pycurl
from io import BytesIO
c = pycurl.Curl()
c.setopt(c.URL,'http://pycurl.io')
buffer = BytesIO()
c.setopt(c.WRITEDATA, buffer)
# Same result if using WRITEFUNCTION instead:
#c.setopt(c.WRITEFUNCTION, buffer.write)
c.perform()
# ok

尝试使用 StringIO 对象将产生错误::

import pycurl
from io import StringIO
c = pycurl.Curl()
c.setopt(c.URL,'http://pycurl.io')
buffer = StringIO()
c.setopt(c.WRITEDATA, buffer)
c.perform()

TypeError: string argument expected, got 'bytes'
Traceback (most recent call last):
  File "/tmp/test.py", line 9, in <module>
    c.perform()
pycurl.error: (23, 'Failed writing body (0 != 168)')

以下习惯用法可用于需要与Python2和Python3兼容的代码:

import pycurl
try:
    # Python 3
    from io import BytesIO
except ImportError:
    # Python 2
    from StringIO import StringIO as BytesIO
c = pycurl.Curl()
c.setopt(c.URL,'http://pycurl.io')
buffer = BytesIO()
c.setopt(c.WRITEDATA, buffer)
c.perform()
# ok
# Decode the response body:
string_body = buffer.getvalue().decode('utf-8')

标题功能

尽管头通常是ASCII文本,但它们仍然返回为 bytes python 3上的实例,因此需要适当的解码。HTTP头按照标准在ISO/IEC 8859-1中编码。

使用时 WRITEHEADER 选项将头文件写入文件,文件应在python 2的二进制模式下打开,并且必须在python 3的二进制模式下打开,与 WRITEDATA .

读取函数

读取函数应以字符串选项所期望的方式提供数据:

  • 在python 2上,数据可以 str 实例,适当编码。

  • 在python 2上,数据可以 unicode 仅包含ASCII码位的实例。

  • 在python 3上,数据可以给出如下 bytes 实例。

  • 在Python 3上。数据可以表示为 str 仅包含ASCII码位的实例。

警告:当与curlopt_postfieldsize一起使用curlopt_readfunction时,如对http所做的那样,请注意传递 编码的 如果正在执行编码,数据将Curl。如果将Unicode字符数而不是编码字节数传递给libcurl,服务器将收到错误的内容长度。或者,如果您的数据只包含ASCII码位,您可以从curlopt_readfunction函数返回Unicode字符串,并让pycurl为您对其进行编码。

pycurl如何处理unicode字符串

如果为pycurl提供了一个包含非ASCII码位的Unicode字符串,因此不能编码为ASCII码,pycurl将向libcurl返回一个错误,libcurl将依次以“读取函数错误/数据错误”等错误使请求失败。Pycrl就会上升 pycurl.error 这是后一条信息。作为问题根本原因的编码异常存储为 sys.last_value .

找出正确的编码

当是一个复杂的问题时,应该使用什么编码。例如,使用HTTP时:

  • URL和PostFields数据必须经过URL编码。URL编码的字符串只有ASCII码位。

  • 报头必须经过ISO/IEC 8859-1编码。

  • 正文的编码在内容类型和内容编码头中指定。

传统的pycurl版本

这里介绍的Unicode处理在pycurl 7.19.3中实现,并支持python 3。在pycurl 7.19.3之前,根本不接受unicode数据:

>>> import pycurl
>>> c = pycurl.Curl()
>>> c.setopt(c.USERAGENT, u'Foo\xa9')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: invalid arguments to setopt

一些GNU/Linux发行版在pycurl 7.19.3之前提供了pycurl的python 3包。这些包包括非官方补丁 ([2], [3]) 它没有正确处理Unicode,并且没有像本文档中描述的那样工作。应该避免使用这种非官方版本的pycurl。

脚注