为何Python requests的PreparedRequest传json时body为bytes、传data为str?
requests库
json=参数主动编码请求体为bytes的原因 这个设计是requests基于使用场景、底层依赖要求、跨版本兼容三个维度做的明确选择,核心逻辑如下:
- 底层传输的硬性要求:HTTP协议在网络中传输的所有内容本质都是字节流,requests依赖的底层HTTP客户端urllib3,要求传入的请求体必须是类bytes类型才能正常发送。
- 抹平Python版本差异:Python2环境中标准库
json.dumps默认返回bytes类型字符串,但Python3环境中json.dumps返回的是Unicode格式的str类型。如果不在JSON处理分支主动做UTF-8编码,Python3环境下构造的请求会在发送阶段触发类型错误。 - 匹配参数的设计定位:
json=参数是requests提供的开箱即用JSON请求构造入口,设计目标就是让用户只需要传入Python可序列化对象,不需要手动处理JSON序列化、字符编码、Content-Type头设置这些重复工作,所以库内部会把所有前置处理做完,最终输出可以直接交给底层传输的标准bytes格式请求体。
和data=参数行为差异的原因
data=参数的定位是通用自定义请求体入口,支持传入字典、字符串、字节、文件对象、迭代器/生成器等多种类型,不会在prepare_body阶段对str类型内容做强制编码:
- 如果传入str类型内容,requests会在实际发起网络请求的最后阶段,按照请求头中指定的字符集做编码,不会在请求预处理阶段提前转换类型
- 如果用户传入的是已经编码完成的bytes、文件流、惰性生成器对象,提前执行编码逻辑反而会破坏原始内容,导致请求发送异常
行为复现
In [8]: requests.Request('GET', 'https://httpbin.org/get', json={'a': 5}).prepare().body Out[8]: b'{"a": 5}' # bytes类型 In [9]: requests.Request('GET', 'https://httpbin.org/get', data=json.dumps({'a': 5})).prepare().body Out[9]: '{"a": 5}' # str类型
对应源码佐证
requests.models中prepare_body方法的源码和注释已经直接说明了这段编码逻辑的设计初衷:
def prepare_body(self, data, files, json=None): """Prepares the given HTTP body data.""" body = None content_type = None if not data and json is not None: # urllib3 requires a bytes-like body. Python 2's json.dumps # provides this natively, but Python 3 gives a Unicode string. content_type = 'application/json' try: body = complexjson.dumps(json, allow_nan=False) except ValueError as ve: raise InvalidJSONError(ve, request=self) if not isinstance(body, bytes): body = body.encode('utf-8') # 主动编码逻辑
内容的提问来源于stack exchange,提问作者nhtrnm
相关产品推荐
相关产品推荐

