web-dev-qa-db-fra.com

Xpath comme une requête pour les dictionnaires python imbriqués

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

Quelque chose comme ça:

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

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

>> {'bacon' : 'foobar'}

Je devais aussi sélectionner des listes imbriquées;)

Cela peut être fait 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ées, je ne changerai donc pas la réponse acceptée.

29
RickyA

Pas tout à fait beau, mais vous pourriez utiliser qch 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 les index, bien sûr ... sans parler du / key trap unutbu indiqué.

10
Johannes Charra

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 pour faire des choses qui prennent 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]
18
nikolay

Il y a la nouvelle bibliothèque jsonpath-rw qui prend en charge la syntaxe JSONPATH mais pour python dictionnaires et arrays , comme vous le souhaitez. 

Donc, 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 autre bibliothèque plus simple prenant également en charge les dictionnaires est python-json-pointer avec une syntaxe plus semblable à XPath.

12
ankostis

Il existe un moyen plus facile de le faire maintenant.

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

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

... terminé. Ou si vous n'aimez pas obtenir vos résultats dans une vue (dictionnaire fusionné qui conserve les chemins), renvoyez-les-en:

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

... et fait. 'valeur' ​​tiendra {{bacon ':' foobar '} dans ce cas.

11
Andrew Kesterson

Si vous préférez la maigreur:

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 seulement avez des dict, alors c'est plus simple:

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

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

2
d1zzyg

Il faudrait travailler davantage sur le fonctionnement du sélecteur semblable à XPath. '/' est une clé de dictionnaire valide. 

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

être manipulé?

foo.select("///")

serait ambigu.

1
unutbu

Une autre alternative (outre celle suggéré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")
1
MarcoS

Existe-t-il une raison pour que vous l'interrogiez comme le motif XPath? Comme l'a suggéré le commentateur de votre question, il ne s'agit que d'un dictionnaire, ce qui vous permet d'accéder aux éléments de manière imbriquée. De plus, étant donné que les données sont sous la forme de JSON, vous pouvez utiliser le module simplejson pour les charger et accéder également aux éléments.

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

1
Senthil Kumaran

dict> json> jmespath

Vous pouvez utiliser JMESPath qui est un langage de requête pour JSON et qui possède une implémentation python .

import jmespath # pip install jmespath

data = {'root': {'section': {'item1': 'value1', 'item2': 'value2'}}}

jmespath.search('root.section.item2', data)
Out[42]: 'value2'

La syntaxe de requête jmespath et les exemples en direct: http://jmespath.org/tutorial.html

dict> xml> xpath

Une autre option serait de convertir vos dictionnaires au format XML en utilisant quelque chose comme dicttoxml puis en utilisant des expressions XPath classiques, par exemple via lxml ou la bibliothèque que vous préférez.

from dicttoxml import dicttoxml  # pip install dicttoxml
from lxml import etree  # pip install lxml

data = {'root': {'section': {'item1': 'value1', 'item2': 'value2'}}}
xml_data = dicttoxml(data, attr_type=False)
Out[43]: b'<?xml version="1.0" encoding="UTF-8" ?><root><root><section><item1>value1</item1><item2>value2</item2></section></root></root>'

tree = etree.fromstring(xml_data)
tree.xpath('//item2/text()')
Out[44]: ['value2']
0
ccpizza
def Dict(var, *arg, **kwarg):
  """ Return the value of an (imbricated) dictionnary, if all fields exist else return "" unless "default=new_value" specified as end argument
      Avoid TypeError: argument of type 'NoneType' is not iterable
      Ex: Dict(variable_dict, 'field1', 'field2', default = 0)
  """
  for key in arg:
    if isinstance(var, dict) and key and key in var:  var = var[key]
    else:  return kwarg['default'] if kwarg and 'default' in kwarg else ""   # Allow Dict(var, tvdbid).isdigit() for example
  return kwarg['default'] if var in (None, '', 'N/A', 'null') and kwarg and 'default' in kwarg else "" if var in (None, '', 'N/A', 'null') else var

foo = {
  'spam':'eggs',
  'morefoo': {
               'bar':'soap',
               'morebar': {'bacon' : 'foobar'}
              }
   }
print Dict(foo, 'morefoo', 'morebar')
print Dict(foo, 'morefoo', 'morebar', default=None)

Avoir une fonction SaveDict (value, var, * arg) pouvant même s’ajouter aux listes de dict ...

0
ZeroQI