简体   繁体   English

urlencode python中的多维字典

[英]urlencode a multidimensional dictionary in python

How can I get a URL-encoded version of a multidimensional dictionary in Python? 如何在Python中获取多维字典的URL编码版本? Unfortunately, urllib.urlencode() only works in a single dimension. 不幸的是, urllib.urlencode()仅适用于单个维度。 I would need a version capable of recursively encoding the dictionary. 我需要一个能够递归编码字典的版本。

For example, if I have the following dictionary: 例如,如果我有以下字典:

{'a': 'b', 'c': {'d': 'e'}}

I want to obtain the following string: 我想获取以下字符串:

a=b&c[d]=e

OK people. 好的人。 I implemented it myself: 我自己实现了:

import urllib

def recursive_urlencode(d):
    """URL-encode a multidimensional dictionary.

    >>> data = {'a': 'b&c', 'd': {'e': {'f&g': 'h*i'}}, 'j': 'k'}
    >>> recursive_urlencode(data)
    u'a=b%26c&j=k&d[e][f%26g]=h%2Ai'
    """
    def recursion(d, base=[]):
        pairs = []

        for key, value in d.items():
            new_base = base + [key]
            if hasattr(value, 'values'):
                pairs += recursion(value, new_base)
            else:
                new_pair = None
                if len(new_base) > 1:
                    first = urllib.quote(new_base.pop(0))
                    rest = map(lambda x: urllib.quote(x), new_base)
                    new_pair = "%s[%s]=%s" % (first, ']['.join(rest), urllib.quote(unicode(value)))
                else:
                    new_pair = "%s=%s" % (urllib.quote(unicode(key)), urllib.quote(unicode(value)))
                pairs.append(new_pair)
        return pairs

    return '&'.join(recursion(d))

if __name__ == "__main__":
    import doctest
    doctest.testmod()

Still, I'd be interested to know if there's a better way to do this. 不过,我有兴趣知道是否有更好的方法来做到这一点。 I can't believe Python's standard library doesn't implement this. 我不敢相信Python的标准库没有实现这一点。

Something like this? 像这样的东西?

a = {'a': 'b', 'c': {'d': 'e'}}

url = urllib.urlencode([('%s[%s]'%(k,v.keys()[0]), v.values()[0] ) if type(v)==dict else (k,v) for k,v in a.iteritems()])

url = 'a=b&c%5Bd%5D=e'

The above solution only works for arrays with depth < 2. The code below will properly urlencode a multidimensional array of any depth. 上述解决方案仅适用于深度<2的数组。下面的代码将正确地对任何深度的多维数组进行urlencode。

#!/usr/bin/env python

import sys
import urllib

def recursive_urlencode(data):
    def r_urlencode(data, parent=None, pairs=None):
        if pairs is None:
            pairs = {}
        if parent is None:
            parents = []
        else:
            parents = parent

        for key, value in data.items():
            if hasattr(value, 'values'):
                parents.append(key)
                r_urlencode(value, parents, pairs)
                parents.pop()
            else:
                pairs[renderKey(parents + [key])] = renderVal(value)

        return pairs
    return urllib.urlencode(r_urlencode(data))


def renderKey(parents):
    depth, outStr = 0, ''
    for x in parents:
        str = "[%s]" if depth > 0 else "%s"
        outStr += str % renderVal(x)
        depth += 1
    return outStr


def renderVal(val):
    return urllib.quote(unicode(val))


def main():
    print recursive_urlencode(payload)


if __name__ == '__main__':
    sys.exit(main())

Based on the code of @malaney, I think that the code below emulates the PHP function http_build_query() quite well. 根据@malaney的代码,我认为下面的代码很好地模拟了PHP函数http_build_query()

#!/usr/bin/env python3

import urllib.parse

def http_build_query(data):
    parents = list()
    pairs = dict()

    def renderKey(parents):
        depth, outStr = 0, ''
        for x in parents:
            s = "[%s]" if depth > 0 or isinstance(x, int) else "%s"
            outStr += s % str(x)
            depth += 1
        return outStr

    def r_urlencode(data):
        if isinstance(data, list) or isinstance(data, tuple):
            for i in range(len(data)):
                parents.append(i)
                r_urlencode(data[i])
                parents.pop()
        elif isinstance(data, dict):
            for key, value in data.items():
                parents.append(key)
                r_urlencode(value)
                parents.pop()
        else:
            pairs[renderKey(parents)] = str(data)

        return pairs
    return urllib.parse.urlencode(r_urlencode(data))

if __name__ == '__main__':
    payload = {
        'action': 'add',
        'controller': 'invoice',
        'code': 'debtor',
        'InvoiceLines': [
            {'PriceExcl': 150, 'Description': 'Setupfee'},
            {'PriceExcl':49.99, 'Description':'Subscription'}
        ],
        'date': '2016-08-01',
        'key': 'Yikes&ampersand'
    }
    print(http_build_query(payload))

    payload2 = [
        'item1',
        'item2'
    ]
    print(http_build_query(payload2))

what about json.dumps and json.loads? 那json.dumps和json.loads呢?

d = {'a': 'b', 'c': {'d': 'e'}}
s = json.dumps(d)  # s: '{"a": "b", "c": {"d": "e"}}'
json.loads(s)  # -> d

I think the code below may be what you want 我认为下面的代码可能就是你想要的

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__':
    params = {'name': 'interface_name', 'interfaces': [{'interface': 'inter1'}, {'interface': 'inter2'}]}
    print(url_encoder(params))

the output is 输出是

interfaces%5B1%5D%5Binterface%5D=inter2&name=interface_name&interfaces%5B0%5D%5Binterface%5D=inter1

which is look like 这看起来像

interfaces[1][interface]=inter2&name=interface_name&interfaces[0][interface]=inter1

PS: you may want use OrderDict to replace dict above PS:您可能希望使用OrderDict替换上面的dict

The function get_encoded_url_params() takes a dict as argument and returns url encoded form of the dict. 函数get_encoded_url_params()接受一个dict作为参数,并返回dict的url编码形式。

def get_encoded_url_params(d):
    """URL-encode a nested dictionary.
    :param d = dict
    :returns url encoded string with dict key-value pairs as query parameters
    e.g.
    if d = { "addr":{ "country": "US", "line": ["a","b"] },
            "routing_number": "011100915", "token": "asdf"
        }
    :returns 'addr[country]=US&addr[line][0]=a&addr[line][1]=b&routing_number=011100915&token=asdf'
    or 'addr%5Bcountry%5D=US&addr%5Bline%5D%5B0%5D=a&addr%5Bline%5D%5B1%5D=b&routing_number=011100915&token=asdf'
    (which is url encoded form of the former using quote_plus())
    """

    def get_pairs(value, base):
        if isinstance(value, dict):
            return get_dict_pairs(value, base)
        elif isinstance(value, list):
            return get_list_pairs(value, base)
        else:
            return [base + '=' + str(value)]
            # use quote_plus() to get url encoded string
            # return [quote_plus(base) + '=' + quote_plus(str(value))]

    def get_list_pairs(li, base):
        pairs = []
        for idx, value in enumerate(li):
            new_base = base + '[' + str(idx) + ']'
            pairs += get_pairs(value, new_base)
        return pairs

    def get_dict_pairs(d, base=''):
        pairs = []
        for key, value in d.items():
            new_base = key if base == '' else base + '[' + key + ']'
            pairs += get_pairs(value, new_base)
        return pairs

    return '&'.join(get_dict_pairs(d))

what about this simplified version: 这个简化版本怎么样:

def _clean(value):
    return urllib.quote(unicode(value))

'&'.join([ v for val in [[ "%s[%s]=%s"%(k,ik, _(iv)) 
    for ik, iv in v.items()] if type(v)==dict else ["%s=%s"%(k,_(v))] 
    for k,v in data.items() ] 
    for v in val ])

I agree is not readable, maybe flattening the list can be better done with itertools.chain instead of another list comprehension. 我同意是不可读的,也许使用itertools.chain而不是其他列表理解可以更好地完成列表的扁平化。

This only goes 1 level deeper, yours can go N levels deeper if you would add some logic to manage N numbers of "[%s]" depending on the level, but I guess is not that necesary 如果您根据水平添加一些逻辑来管理N个“[%s]”,那么这只会更深一层,你可以更深入N级,但我想这不是必要的

If you want to convert python dict/list/nested to PHP Array like urlencoded string. 如果你想将python dict / list / nested转换为PHP数组,如urlencoded string。

In python, most of the data type you want to convert to urlencoded maybe: dict list tuple nested of them , Like 在python中,你要转换为urlencoded的大多数数据类型可能是: dict list tuple nested of them ,Like

a = [1, 2]
print(recursive_urlencode(a))
# 0=1&1=2


a2 = (1, '2')
print(recursive_urlencode(a2))
# 0=1&1=2


b = {'a': 11, 'b': 'foo'}
print(recursive_urlencode(b))
# a=11&b=foo


c = {'a': 11, 'b': [1, 2]}
print(recursive_urlencode(c))
# a=11&b[0]=1&b[1]=2


d = [1, {'a': 11, 'b': 22}]
print(recursive_urlencode(d))
# 0=1&1[a]=11&1[b]=22


e = {'a': 11, 'b': [1, {'c': 123}, [3, 'foo']]}
print(recursive_urlencode(e))
# a=11&b[0]=1&b[1][c]=123&b[2][0]=3&b[2][1]=foo

https://github.com/Viky-zhang/to_php_post_arr https://github.com/Viky-zhang/to_php_post_arr

PS some code from: https://stackoverflow.com/a/4014164/2752670 PS部分代码来自: https//stackoverflow.com/a/4014164/2752670

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM