原创作品:首发u3v3, 转载请保留

地址:https://www.u3v3.com/ar/1311

作者ID:Yi_Zhi_Yu

首发日期:2017.4.13

Python学习群:278529278 (欢迎交流)


前言

在PHP中, 我们使用curl 扩展发送post请求时, 可以通过http_build_query 来构造多维的 post 参数, 用法如下

$request_params = [
    'name' => 'Yi_Zhi_Yu',
    'scores' => [
        ['name' => 'English', 'score'=>100],
        ['name' => 'Math', 'score'=>100]
    ]];
echo http_build_query($request_params);

输出如下

user=Yi_Zhi_Yu&info%5B0%5D%5Bage%5D=27&info%5B0%5D%5Bsex%5D=man

通过url decode, 即

user=Yi_Zhi_Yu&info[0][age]=27&info[0][sex]=man

这就是post时, 我们需要发送的post body 的内容

PHP 里有http_build_query, 那python中呢,

当然没了, 要不然我就不用自己实现了

问题

python 里, 我们发送post的时候, 如果需要将post body 做url encode, 有一个urllib2模块可以用

if __name__ == '__main__':
    request_params = {'name': 'Yi_Zhi_Yu', 'score': 100}
    print(urllib.parse.urlencode(request_params))
    # name=Yi_Zhi_Yu&score=100

那如果 post 的结构是多维的呢? 看下

if __name__ == '__main__':
    request_params = {
            'name': 'Yi_Zhi_Yu',
            'scores':[
                {'name': 'English', 'score': 100},
                {'name': 'Math', 'score': 100}
                ]
            }
    print(urllib.parse.urlencode(request_params))

输出如下

name=Yi_Zhi_Yu&scores=%5B%7B%27name%27%3A+%27English%27%2C+%27score%27%3A+100%7D%2C+%7B%27name%27%3A+%27Math%27%2C+%27score%27%3A+100%7D%5D

decode 可视化后:

name=Yi_Zhi_Yu&scores=[{'name': 'English', 'score': 100}, {'name': 'Math', 'score': 100}]

name 还算正常, scores直接是个json格式

post 请求发送出去后, 需要服务端 decode 才能用

我们期望的格式应该是类似于PHP http_build_query的结果

name=Yi_Zhi_Yu&scores[0][name]=English&scores[0][score]=100&scores[1][name]=Math&scores[1][score]=100

服务端通过POST获取后, 直接就可以作为数组使用

为此, 我实现了一个简单的版本

解决

直接看代码

import urllib.parse

def url_encoder(params):
    g_encode_params = {}

    def _encode_params(params, p_key=None):
        encode_params = {}
        if isinstance(params, dict):
            for key in params:
                encode_key = '{}[{}]'.format(p_key,key)
                encode_params[encode_key] = params[key]
        elif isinstance(params, (list, tuple)):
            for offset,value in enumerate(params):
                encode_key = '{}[{}]'.format(p_key, offset)
                encode_params[encode_key] = value
        else:
            g_encode_params[p_key] = params

        for key in encode_params:
            value = encode_params[key]
            _encode_params(value, key)

    if isinstance(params, dict):
        for key in params:
            _encode_params(params[key], key)

    return urllib.parse.urlencode(g_encode_params)

看测试代码

if __name__ == '__main__':
    request_params = {'name': 'Yi_Zhi_Yu',
            'scores':[
                {'name': 'English', 'score': 100},
                {'name': 'Math', 'score': 100}
                ]
            }
    #print(urllib.parse.urlencode(request_params))
    print(url_encoder(request_params))

输出结果经过decode后如下

scores[1][score]=100&scores[0][score]=100&scores[1][name]=Math&scores[0][name]=English&name=Yi_Zhi_Yu

服务端在接受到这个POST bod以后, 也可以直接作为数组使用, 请求格式与PHP的 http_build_query基本类似

不过这里有个问题是, url encode后的参数顺序与原始的request params 不一样了

这个是因为python的dict本身是无序的, 如果要求有序, 大家可以把默认的dict 替换为OrderDict

这个作为一个小作业吧, 后续有时间, 我会贴出来结果的

:)

误区

这里还有一个关于PHP的常见误区, 当客户端发起的post请求中, headercontent-type 被设置成了 application/json时, 很多人认为, 在PHP的Server端, 用$_POST就能获取json的请求格式数据了

然而并非如此, 这种时候, PHP只能通过

$json = file_get_contents('php://input');
$obj = json_decode($json);

获取 json 的数据

right