2011-09-06 11 views
25

C'è un modo per definire una query di tipo XPath per i dizionari python nidificati.Xpath come query per dizionari python nidificati

Qualcosa di simile a questo:

foo = { 
    'spam':'eggs', 
    'morefoo': { 
       'bar':'soap', 
       'morebar': {'bacon' : 'foobar'} 
       } 
    } 

print(foo.select("/morefoo/morebar")) 

>> {'bacon' : 'foobar'} 

Ho anche bisogno di selezionare liste annidate;)

Questo può essere fatto facilmente con la soluzione @ di Jellybean:

def xpath_get(mydict, path): 
    elem = mydict 
    try: 
     for x in path.strip("/").split("/"): 
      try: 
       x = int(x) 
       elem = elem[x] 
      except ValueError: 
       elem = elem.get(x) 
    except: 
     pass 

    return elem 

foo = { 
    'spam':'eggs', 
    'morefoo': [{ 
       'bar':'soap', 
       'morebar': { 
          'bacon' : { 
             'bla':'balbla' 
            } 
          } 
       }, 
       'bla' 
       ] 
    } 

print xpath_get(foo, "/morefoo/0/morebar/bacon") 

[EDIT 2016] Questo domanda e la risposta accettata sono antiche. Le risposte più recenti possono fare il lavoro meglio della risposta originale. Tuttavia non li ho testati, quindi non cambierò la risposta accettata.

+0

Perché non usare 'foo ['morefoo'] ['morebar']'? – MarcoS

+3

perché voglio fare: def bla (query): data.select (query) – RickyA

+0

@MarcoS Sarebbe più interessante con le liste in cui il percorso microlingua restituirebbe più elementi. –

risposta

8

Non esattamente bella, ma è possibile utilizzare STH come

def xpath_get(mydict, path): 
    elem = mydict 
    try: 
     for x in path.strip("/").split("/"): 
      elem = elem.get(x) 
    except: 
     pass 

    return elem 

Questo non supporta roba XPath come indici, naturalmente ... per non parlare del / chiave unutbu trappola indicato.

+0

Nel 2011 forse non c'erano tante opzioni quante sono oggi, ma nel 2014, penso che risolvere il problema in questo modo non sia elegante e dovrebbe essere evitato. – nikolay

+8

@nikolay è solo un'ipotesi o ci sono soluzioni che risolvono meglio questo problema? –

1

Più lavoro dovrebbe essere messo in come il selettore tipo XPath avrebbe funzionato. '/' è una chiave di dizionario valida, così come sarebbe

foo={'/':{'/':'eggs'},'//':'ham'} 

essere gestiti?

foo.select("///") 

sarebbe ambiguo.

+0

Sì, avresti bisogno di un parser per questo. Ma quello che sto chiedendo è un metodo _like_ xpath. "morefoo.morebar" va bene per me. – RickyA

+2

@RickyA: ''.'' è anche un valore chiave del dizionario. Lo stesso problema potrebbe esistere. 'foo.select ('...')' sarebbe ambiguo. – unutbu

1

C'è qualche motivo per voi per interrogarlo come il modello XPath? Come ha suggerito il commentatore alla tua domanda, è solo un dizionario, quindi puoi accedere agli elementi in modo nido. Inoltre, considerando che i dati sono sotto forma di JSON, è possibile utilizzare il modulo simplejson per caricarlo e accedere anche agli elementi.

C'è questo progetto JSONPATH, che sta cercando di aiutare le persone a fare il contrario di ciò che si intende fare (dato un XPATH, come renderlo facilmente accessibile tramite oggetti python), che sembra più utile.

+0

Il motivo è che voglio dividere i dati e la query. Voglio essere flessibile nella parte della query. Se accedo ad esso in modo annidato, la query è codificata nel programma. – RickyA

+0

@RickyA, nell'altro commento si dice morefoo.morebar va bene. Hai controllato il progetto JSONPATH (scarica e guarda la fonte e i test). –

+0

Ho dato un'occhiata a JSONPATH, ma il mio input non è text/json. Sono dizionari nidificati. – RickyA

1

Un'altra alternativa (oltre a quello suggerito da jellybean) è questo:

def querydict(d, q): 
    keys = q.split('/') 
    nd = d 
    for k in keys: 
    if k == '': 
     continue 
    if k in nd: 
     nd = nd[k] 
    else: 
     return None 
    return nd 

foo = { 
    'spam':'eggs', 
    'morefoo': { 
       'bar':'soap', 
       'morebar': {'bacon' : 'foobar'} 
       } 
    } 
print querydict(foo, "/morefoo/morebar") 
11

C'è un modo più semplice per farlo ora.

http://github.com/akesterson/dpath-python

$ easy_install dpath 
>>> dpath.util.search(YOUR_DICTIONARY, "morefoo/morebar") 

... fatto. O se non vi piace ottenere i risultati di nuovo in una vista (dizionario fusione che mantiene i percorsi), cederli invece:

$ easy_install dpath 
>>> for (path, value) in dpath.util.search(YOUR_DICTIONARY, "morefoo/morebar", yielded=True) 

... e fatto. 'valore' terrà {'bacon': 'foobar'} in quel caso.

+0

L'istruzione iterata non viene eseguita --- non c'è un corpo nell'istruzione for. – Mittenchops

10

V'è la più recente libreria jsonpath-rw sostenere una sintassi JSONPATH ma per Python dizionari e array, come si voleva.

Così il vostro primo esempio diventa:

from jsonpath_rw import parse 

print(parse('$.morefoo.morebar').find(foo)) 

E il secondo:

print(parse("$.morefoo[0].morebar.bacon").find(foo)) 

PS: Una libreria semplice alternativa anche sostenendo dizionari è python-json-pointer con una maggiore XPath simile sintassi.

+0

Nota che jsonpath usa eval e jsonpath-rw sembra non mantenuto (dice anche che alcune funzionalità sono mancanti, ma non l'ho provato). –

15

Una delle migliori librerie che sono stato in grado di identificare, che, inoltre, è stata sviluppata molto attivamente, è un progetto estratto da boto: JMESPath. Ha una sintassi molto potente di fare cose che normalmente richiedono pagine di codice da esprimere.

ecco alcuni esempi:

search('foo | bar', {"foo": {"bar": "baz"}}) -> "baz" 
search('foo[*].bar | [0]', { 
    "foo": [{"bar": ["first1", "second1"]}, 
      {"bar": ["first2", "second2"]}]}) -> ["first1", "second1"] 
search('foo | [0]', {"foo": [0, 1, 2]}) -> [0] 
0

Se concisione è la vostra fantasia:

def xpath(root, path, sch='/'): 
    return reduce(lambda acc, nxt: acc[nxt], 
        [int(x) if x.isdigit() else x for x in path.split(sch)], 
        root) 

Naturalmente, se si solo avere dicts, allora è semplice:

def xpath(root, path, sch='/'): 
    return reduce(lambda acc, nxt: acc[nxt], 
        path.split(sch), 
        root) 

Buona fortuna a trovare eventuali errori nel tuo percorso spec tho ;-)