2010-10-25 13 views
6

¿Cómo puedo obtener una versión con codificación URL de un diccionario multidimensional en Python? Desafortunadamente, urllib.urlencode() solo funciona en una sola dimensión. Necesitaría una versión capaz de codificar recursivamente el diccionario.urlencode un diccionario multidimensional en python

Por ejemplo, si tengo el siguiente diccionario:

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

Quiero obtener la siguiente secuencia:

a=b&c[d]=e 

Respuesta

8

OK people. Lo implementé yo mismo:

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

Aún así, me gustaría saber si hay una mejor manera de hacerlo. No puedo creer que la biblioteca estándar de Python no implemente esto.

+7

¿Por qué debería? Esto no es de ninguna manera un formato estándar. Lo que aparece entre corchetes es una peculiaridad específica de PHP que no está presente en la mayoría de los otros lenguajes/marcos o en cualquier estándar web. – bobince

+4

Puede que no sea un estándar de macarrones sagrados de sangre pura descrito por IETF/W3C, pero se usa tan comúnmente hoy en día que no estar incluido en la biblioteca estándar de Python desafía mi comprensión. He desarrollado aplicaciones web en varias plataformas e idiomas, y esta siempre ha sido la convención. Y esto incluye entornos basados ​​en Python como Django: así que no, ya no es solo un PHP. – pablobm

+1

Para que la gente que viene más tarde lo sepa, este código casi funciona, pero no funciona para objetos con> 1 nivel de anidación. Por lo tanto, los objetos con> 1 nivel de anidamiento se reinician en la parte superior del hash. –

3

Algo como esto?

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

Miedo no. Tenga en cuenta la diferencia entre 'a = b & c [d] = e' y su propuesta 'a = b & c% 5Bd% 5D = e'. Ese escape no debería estar allí. – pablobm

+0

Luego tendrá que iterar sobre el diccionario como lo hice y urlencode cada elemento por separado ... – eumiro

+0

Esto solo toma el primer valor interno en un dict pero me gusta la forma en que lo hizo así que +1 para usted jeje – Hassek

0

¿qué pasa con 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 
+0

No. Qué Quiero es URL-encode. No más, no menos – pablobm

0

¿qué pasa con esta versión simplificada:

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

Estoy de acuerdo no es legible, tal vez el aplanamiento de la lista puede ser mejor hecho con itertools.chain en lugar de otra lista por comprensión.

Esto sólo quedándose en 1 nivel más profundo, el suyo puede ir N niveles más profundos si desea añadir un poco de lógica para gestionar N números de "[% s]" en función del nivel, pero supongo que no es tan NECESARIO

2

La solución anterior solo funciona para matrices con profundidad < 2. El código siguiente urlencode correctamente una matriz multidimensional de cualquier profundidad.

#!/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()) 
+0

Esta función parece pasar la cadena a través de Unicode dos veces. Entonces un '!' se convierte en '% 25% 21' no simplemente '% 21' – deweydb

1

Basado en el código de @malaney, creo que el código de abajo emula la función de PHP http_build_query() bastante bien.

#!/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

Creo que el código de abajo puede ser lo que quiere

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

la salida es

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

que es parecerse a

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

PD: es posible que desee use OrderDict para reemplazar dict anterior

-1

Si desea convertir dict/list/anidado a PHP Array como string urlencoded.

en Python, la mayoría de los tipo de datos que desea convertir a urlencoded quizá: dictlisttuplenested of them, igual que

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. algún código de: https://stackoverflow.com/a/4014164/2752670