2011-09-06 5 views
25

est-il un moyen de définir une requête de type XPath pour les dictionnaires de python imbriqués.Xpath comme requête pour les dictionnaires de python imbriqués

Quelque chose comme ceci:

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

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

>> {'bacon' : 'foobar'} 

J'ai aussi besoin de sélectionner des listes imbriquées;)

Cela peut se faire facilement avec la solution de @ 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] Cette question et la réponse acceptée sont anciennes. Les nouvelles réponses peuvent faire le travail mieux que la réponse originale. Cependant, je ne les ai pas testés, donc je ne changerai pas la réponse acceptée.

+0

Pourquoi ne pas utiliser 'foo ['morefoo'] ['morebar']'? – MarcoS

+3

parce que je veux faire: def bla (requête): data.select (requête) – RickyA

+0

@MarcoS Il serait plus intéressant avec les listes où le chemin microlanguage renverrait plusieurs éléments. –

Répondre

8

Pas exactement belle, mais vous pouvez utiliser STH comme

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

    return elem 

Cela ne supporte pas les choses XPath comme indices, bien sûr ... sans oublier le piège clé / unutbu indiqué.

+0

En 2011 peut-être il n'y avait pas autant d'options qu'aujourd'hui, mais en 2014, je pense, résoudre le problème de cette façon n'est pas élégant et devrait être évité. – nikolay

+8

@nikolay est-ce juste une supposition ou y at-il des solutions qui résolvent cela plus joliment? –

1

Plus de travail devrait être mis sur la façon dont le sélecteur comme XPath fonctionnerait. '/' est une clé de dictionnaire valide, alors comment serait

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

être traitées?

foo.select("///") 

est ambigu.

+0

Oui, vous auriez besoin d'un analyseur pour cela. Mais ce que je demande, c'est une méthode xpath _like_. "morefoo.morebar" va bien par moi. – RickyA

+2

@RickyA: ''.'' est aussi une clé de dictionnaire de valeur. Le même problème existerait. 'foo.select ('...')' serait ambigu. – unutbu

1

Y at-il une raison quelconque pour vous à la requête, il la manière comme le modèle XPath? En tant que commentateur à votre question suggère, juste un dictionnaire, vous pouvez accéder aux éléments de manière nid. En outre, étant donné que les données sont sous forme de JSON, vous pouvez utiliser le module simplejson pour le charger et accéder aux éléments aussi.

Il ya ce projet JSONPATH, qui essaie d'aider les gens à faire l'inverse de ce que vous avez l'intention de faire (donné un XPATH, comment le rendre facilement accessible via des objets python), ce qui semble plus utile.

+0

La raison est que je veux diviser les données et la requête. Je veux être flexible dans la partie requête. Si j'y accède de la manière imbriquée, la requête est codée en dur dans le programme. – RickyA

+0

@RickyA, dans l'autre commentaire, vous dites morefoo.morebar est très bien. Avez-vous vérifié le projet JSONPATH (Télécharger et regarder la source et les tests). –

+0

J'ai jeté un oeil à JSONPATH, mais mon entrée n'est pas text/json. C'est des dictionnaires imbriqués. – RickyA

1

Une autre alternative (en plus de celle proposée par jellybean) est la suivante:

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

Il y a un moyen plus facile de le faire maintenant.

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

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

... fait. Ou si vous ne l'aimez pas obtenir vos résultats en arrière dans une vue (dictionnaire fusionné qui conserve les chemins), les céder à la place:

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

... et fait. « Valeur » tiendra { « lard »: « foobar »} dans ce cas.

+0

L'instruction itérée ne s'exécute pas --- il n'y a pas de corps dans l'instruction for. – Mittenchops

10

Il est la plus récente jsonpath-rw bibliothèque supportant une syntaxe JSONPATH mais pour python dictionnaires et tableaux, comme vous le souhaitiez.

Ainsi, votre 1er exemple devient:

from jsonpath_rw import parse 

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

Et le 2ème:

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

PS: Une alternative bibliothèque plus simple supportant également des dictionnaires est python-json-pointer avec un plus semblable à XPath syntaxe.

+0

Notez que jsonpath utilise eval et jsonpath-rw ne semble pas maintenu (il dit aussi que certaines fonctionnalités sont manquantes, mais je ne l'ai pas essayé). –

15

L'une des meilleures bibliothèques que j'ai pu identifier, qui, de plus, est très activement développée, est un projet extrait de boto: JMESPath. Il a une syntaxe très puissante de faire des choses qui prendraient normalement des pages de code à exprimer.

Voici quelques exemples:

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

Si laconisme est votre fantaisie:

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) 

Bien sûr, si vous que ont dicts, il est plus simple:

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

Bonne chance pour trouver les erreurs dans votre chemin spec tho ;-)

Questions connexes