2011-09-06 20 views
25

¿Hay alguna manera de definir una consulta de tipo XPath para diccionarios de python anidados?Xpath como consulta para diccionarios de python anidados

Algo como esto:

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

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

>> {'bacon' : 'foobar'} 

También necesitaba para seleccionar las listas anidadas;)

Esto se puede hacer fácilmente con una solución 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") 

[EDICIÓN 2016] Este pregunta y la respuesta aceptada son antiguas. Las respuestas más nuevas pueden hacer el trabajo mejor que la respuesta original. Sin embargo, no los probé, así que no cambiaré la respuesta aceptada.

+0

¿Por qué no usar 'foo ['morefoo'] ['morebar']'? – MarcoS

+3

porque quiero hacer: def bla (consulta): data.select (consulta) – RickyA

+0

@MarcoS Sería más interesante con las listas donde el microlenguaje de ruta devolvería varios elementos. –

Respuesta

8

No exactamente hermosa, pero es posible utilizar algo como

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

    return elem 

Este no es compatible con la materia XPath como índices, por supuesto ... por no hablar de la / unutbu trampa tecla indicada.

+0

En 2011 tal vez no hubo tantas opciones como las hay hoy, pero en 2014, creo, resolver el problema de esta manera no es elegante y debe evitarse. – nikolay

+8

@nikolay ¿es solo una suposición o hay soluciones que resuelvan esto de forma más agradable? –

1

Más trabajo tendría que ponerse en cómo funcionaría el selector de XPath. '/' es una clave válida diccionario, así que ¿cómo habría

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

ser manejado?

foo.select("///") 

serían ambiguos.

+0

Sí, necesitarías un analizador para eso. Pero lo que estoy preguntando es por un método xpath _like_. "morefoo.morebar" está bien para mí. – RickyA

+2

@RickyA: ''.'' también es una clave del diccionario de valores. El mismo problema existiría. 'foo.select ('...')' sería ambiguo. – unutbu

1

¿Hay alguna razón para que usted lo consulte de la misma manera que el patrón XPath? Como lo sugirió el comentarista de su pregunta, es solo un diccionario, por lo que puede acceder a los elementos de una manera nida. Además, considerando que los datos tienen la forma de JSON, puede usar el módulo simplejson para cargarlo y acceder a los elementos también.

Existe este proyecto JSONPATH, que trata de ayudar a las personas a hacer lo contrario de lo que pretendes hacer (dado un XPATH, cómo hacer que sea fácilmente accesible a través de objetos de pitón), lo que parece más útil.

+0

La razón es que quiero dividir los datos y la consulta. Quiero ser flexible en la parte de consulta. Si accedo al modo anidado, la consulta está codificada en el programa. – RickyA

+0

@RickyA, en el otro comentario dices morefoo.morebar está bien. ¿Revisó el proyecto JSONPATH (descargue y vea la fuente y las pruebas). –

+0

Eché un vistazo a JSONPATH, pero mi entrada no es text/json. Son diccionarios anidados. – RickyA

1

Otra alternativa (además de la sugerida por jellybean) es la siguiente:

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

hay una manera más fácil de hacer esto ahora.

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

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

... hecho. O si no te gusta conseguir sus resultados de nuevo en una vista (diccionario fusionada que conserva los caminos), rendimiento de ellos en su lugar:

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

... y hecho. 'value' mantendrá {'bacon': 'foobar'} en ese caso.

+0

La instrucción iterada no se ejecuta --- no hay cuerpo para la instrucción for. – Mittenchops

10

Existe la nueva biblioteca de apoyo jsonpath-rw una sintaxis JSONPATH pero para pitón diccionarios y matrices, como usted deseaba.

Así que su primera ejemplo, se convierte en:

from jsonpath_rw import parse 

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

Y la segunda:

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

PS: Una biblioteca simple alternativa que también apoya los diccionarios es python-json-pointer con una más similar a XPath sintaxis.

+0

Tenga en cuenta que jsonpath utiliza eval y jsonpath-rw parece no mantenido (también dice que faltan algunas características, pero no lo he probado). –

15

Una de las mejores bibliotecas que he podido identificar, que, además, se desarrolla muy activamente, es un proyecto extraído de boto: JMESPath. Tiene una sintaxis muy poderosa de hacer cosas que normalmente tomarían páginas de código para expresar.

Éstos son algunos ejemplos:

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 concisión es su fantasía:

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) 

Por supuesto, si única tener dicts, entonces es más simple:

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

Buena suerte para encontrar cualquier error en su camino; th ;-)

Cuestiones relacionadas