2010-10-25 25 views
6

Come posso ottenere una versione con codifica URL di un dizionario multidimensionale in Python? Sfortunatamente, urllib.urlencode() funziona solo in una singola dimensione. Avrei bisogno di una versione in grado di codificare ricorsivamente il dizionario.urlencode un dizionario multidimensionale in python

Per esempio, se ho il seguente dizionario:

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

voglio ottenere la seguente stringa:

a=b&c[d]=e 

risposta

8

OK persone. L'ho implementato personalmente:

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

Tuttavia, sarei interessato a sapere se c'è un modo migliore per farlo. Non posso credere che la libreria standard di Python non la implementa.

+7

Perché dovrebbe? Questo non è in alcun modo un formato standard. La cosa con le parentesi quadre è una peculiarità specifica di PHP non presente nella maggior parte degli altri linguaggi/framework o standard web. – bobince

+4

Potrebbe non essere uno standard santo-macaronese supportato da IETF/W3C puro sangue, ma è così comunemente usato al giorno d'oggi che il suo non essere incluso nella libreria standard di Python sfugge alla mia comprensione. Ho sviluppato applicazioni Web in un certo numero di piattaforme e lingue, e questa è sempre stata la convenzione. E questo include ambienti basati su Python come Django: così no, non è più una cosa solo per PHP. – pablobm

+1

Solo così le persone che vengono qui in seguito sono consapevoli, questo codice si avvicina al funzionamento ma non funziona per oggetti con> 1 livello di nidificazione. Quindi gli oggetti con> 1 livello di nidificazione tornano in cima all'hash. –

3

qualcosa di simile?

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

Non temendo. Si noti la differenza tra 'a = b & c [d] = e' e la tua proposta 'a = b & c% 5Bd% 5D = e'. Quella fuga non dovrebbe essere lì. – pablobm

+0

Quindi dovrai eseguire un'iterazione sul dizionario come ho fatto io e urlencode ogni elemento separatamente ... – eumiro

+0

Questo prende solo il primo valore interno in un dettato ma mi piace come l'hai fatto così +1 per te hehe – Hassek

0

E a proposito di json.dumps e json.loads?

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

No. Cosa Voglio che sia la codifica dell'URL. Niente più, niente di meno – pablobm

0

cosa su questa versione semplificata:

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

Io sono d'accordo non è leggibile, forse appiattendo la lista può essere meglio fatto con itertools.chain al posto di un altro elenco di comprensione.

Questo va solo 1 livello più profondo, la vostra può andare N livelli più profondo se si desidera aggiungere un po 'di logica per gestire N numeri di "[% s]" a seconda del livello, ma credo che non è necesary

2

La soluzione di cui sopra funziona solo per gli array con profondità < 2. Il codice seguente urlencode correttamente una matrice multidimensionale di qualsiasi profondità.

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

Questa funzione sembra passare la stringa attraverso unicode due volte. Quindi un '!' diventa '% 25% 21' non semplicemente '% 21' – deweydb

1

In base al codice di @malaney, penso che il codice qui sotto emula la funzione PHP http_build_query() abbastanza bene.

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

Credo che il codice qui sotto può essere ciò che si desidera

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

l'uscita è

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

che è simile

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

PS: si può decidere utilizzare OrderDict per sostituire dict sopra

-1

Se si desidera convertire python dict/list/nested in PHP Array come stringa urlencoded.

in Python, la maggior parte dei dati di tipo che si desidera convertire in urlencoded forse: dictlisttuplenested of them, come

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. qualche codice da: https://stackoverflow.com/a/4014164/2752670