urlencode многомерный словарь в Python - PullRequest
7 голосов
/ 25 октября 2010

Как я могу получить URL-кодированную версию многомерного словаря в Python?К сожалению, urllib.urlencode() работает только в одном измерении.Мне нужна версия, способная рекурсивно кодировать словарь.

Например, если у меня есть следующий словарь:

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

Я хочу получить следующую строку:

a=b&c[d]=e

Ответы [ 9 ]

10 голосов
/ 25 октября 2010

ОК, люди.Я реализовал это сам:

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 не реализует это.

3 голосов
/ 25 октября 2010

Как то так?

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'
2 голосов
/ 10 мая 2012

Приведенное выше решение работает только для массивов с глубиной <2. Код ниже правильно кодирует многомерный массив любой глубины. </p>

#!/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())
1 голос
/ 22 августа 2016

Основываясь на коде @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))
0 голосов
/ 08 мая 2019

Функция get_encoded_url_params () принимает dict в качестве аргумента и возвращает закодированную в форме url форму 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))
0 голосов
/ 19 декабря 2017

Если вы хотите преобразовать python dict / list / nested в PHP Array, как строку с 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

P.S. некоторый код от: https://stackoverflow.com/a/4014164/2752670

0 голосов
/ 11 апреля 2017

Я думаю, что приведенный ниже код может быть тем, что вы хотите

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 выше

0 голосов
/ 22 сентября 2011

как насчет этой упрощенной версии:

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 вместо другого понимания списка.идет на 1 уровень глубже, ваш может пойти на N уровней глубже, если вы добавите некоторую логику для управления N числами «[% s]» в зависимости от уровня, но я не думаю, что это необходимо

0 голосов
/ 25 октября 2010

как насчет json.dumps и json.loads?

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