python中的urlencode多维字典

发布于 2024-09-29 16:06:21 字数 236 浏览 8 评论 0原文

如何在 Python 中获取多维字典的 URL 编码版本?不幸的是,urllib.urlencode()仅适用于单个维度。我需要一个能够对字典进行递归编码的版本。

例如,如果我有以下字典:

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

我想获取以下字符串:

a=b&c[d]=e

How can I get a URL-encoded version of a multidimensional dictionary in Python? Unfortunately, urllib.urlencode() only works in a single dimension. 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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

薄暮涼年 2024-10-06 16:06:21

好的人们。我自己实现了它:

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()

不过,我很想知道是否有更好的方法来做到这一点。我不敢相信 Python 的标准库没有实现这个。

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.

白鸥掠海 2024-10-06 16:06:21

像这样的东西吗?

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'

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'
对岸观火 2024-10-06 16:06:21

基于@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&ersand'
    }
    print(http_build_query(payload))

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

Based on the code of @malaney, I think that the code below emulates the PHP function http_build_query() quite well.

#!/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&ersand'
    }
    print(http_build_query(payload))

    payload2 = [
        'item1',
        'item2'
    ]
    print(http_build_query(payload2))
风向决定发型 2024-10-06 16:06:21

我认为下面的代码可能是您想要的

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)) 

输出,

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

它看起来像

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

PS:您可能需要使用 OrderDict 来替换上面的 dict

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

你与清晨阳光 2024-10-06 16:06:21

上述解决方案仅适用于深度 < 的数组。 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())

The above solution only works for arrays with depth < 2. The code below will properly urlencode a multidimensional array of any depth.

#!/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())
忆离笙 2024-10-06 16:06:21

函数 get_encoded_url_params() 将字典作为参数并返回字典的 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))

The function get_encoded_url_params() takes a dict as argument and returns url encoded form of the dict.

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))
熟人话多 2024-10-06 16:06:21

json.dumps 和 json.loads 怎么样?

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

what about json.dumps and json.loads?

d = {'a': 'b', 'c': {'d': 'e'}}
s = json.dumps(d)  # s: '{"a": "b", "c": {"d": "e"}}'
json.loads(s)  # -> d
爱*していゐ 2024-10-06 16:06:21

这个简化版本怎么样:

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 ])

我同意不可读,也许使用 itertools.chain 而不是另一个列表理解可以更好地压平列表。

这只会更深一层,如果你添加一些逻辑来根据级别管理 N 个“[%s]”,你的可以更深 N 层,但我想这不是必要的

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.

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

上课铃就是安魂曲 2024-10-06 16:06:21

如果你想将 python dict/list/nested 转换为 PHP 数组,如 urlencoded 字符串。

在Python中,大多数要转换为urlencoded的数据类型可能是: dict list tuple nested of them ,就像

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

PS 一些代码: https://stackoverflow.com/a/4014164/2752670

If you want to convert python dict/list/nested to PHP Array like urlencoded string.

In python, most of the data type you want to convert to urlencoded maybe: 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

P.S. some code from: https://stackoverflow.com/a/4014164/2752670

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文