2010-11-08 22 views
11

Tengo la siguiente función que hace un crudo trabajo de análisis de un archivo XML en un diccionario.¿Cómo se puede reescribir esta función para implementar OrderedDict?

Desafortunadamente, dado que los diccionarios de Python no están ordenados, no puedo pasar por los nodos como me gustaría.

Cómo modifico esto para que emita un diccionario ordenado que refleje el orden original de los nodos cuando se realiza un bucle con 'for'.

def simplexml_load_file(file): 
    import collections 
    from lxml import etree 

    tree = etree.parse(file) 
    root = tree.getroot() 

    def xml_to_item(el): 
     item = None 
     if el.text: 
      item = el.text 
     child_dicts = collections.defaultdict(list) 
     for child in el.getchildren(): 
      child_dicts[child.tag].append(xml_to_item(child)) 
     return dict(child_dicts) or item 

    def xml_to_dict(el): 
     return {el.tag: xml_to_item(el)} 

    return xml_to_dict(root) 

x = simplexml_load_file('routines/test.xml') 

print x 

for y in x['root']: 
    print y 

Salidas:

{'root': { 
    'a': ['1'], 
    'aa': [{'b': [{'c': ['2']}, '2']}], 
    'aaaa': [{'bb': ['4']}], 
    'aaa': ['3'], 
    'aaaaa': ['5'] 
}} 

a 
aa 
aaaa 
aaa 
aaaaa 

¿Cómo puedo implementar collections.OrderedDict para que pueda estar seguro de conseguir el orden correcto de los nodos?

archivo XML para referencia:

<root> 
    <a>1</a> 
    <aa> 
     <b> 
      <c>2</c> 
     </b> 
     <b>2</b> 
    </aa> 
    <aaa>3</aaa> 
    <aaaa> 
     <bb>4</bb> 
    </aaaa> 
    <aaaaa>5</aaaaa> 
</root> 
+0

duplicado de http: // stackoverflow. com/questions/4123266/python-looping-seem-to-not-follow-sequence del mismo autor. –

Respuesta

27

Puede usar la nueva subclase OrderedDictdict que se agregó al módulo collections de la biblioteca estándar en la versión 2.7 *. En realidad lo que necesita es una combinación Ordered + defaultdict que no existe, pero que es posible crear una subclase OrderedDict como se ilustra a continuación:

import collections 

class OrderedDefaultdict(collections.OrderedDict): 
    """ A defaultdict with OrderedDict as its base class. """ 

    def __init__(self, default_factory=None, *args, **kwargs): 
     if not (default_factory is None 
       or isinstance(default_factory, collections.Callable)): 
      raise TypeError('first argument must be callable or None') 
     super(OrderedDefaultdict, self).__init__(*args, **kwargs) 
     self.default_factory = default_factory # called by __missing__() 

    def __missing__(self, key): 
     if self.default_factory is None: 
      raise KeyError(key,) 
     self[key] = value = self.default_factory() 
     return value 

    def __reduce__(self): # optional, for pickle support 
     args = (self.default_factory,) if self.default_factory else tuple() 
     return self.__class__, args, None, None, self.iteritems() 

    def __repr__(self): # optional 
     return '%s(%r, %r)' % (self.__class__.__name__, self.default_factory, 
           list(self.iteritems())) 

def simplexml_load_file(file): 
    from lxml import etree 

    tree = etree.parse(file) 
    root = tree.getroot() 

    def xml_to_item(el): 
     item = el.text or None 
     child_dicts = OrderedDefaultdict(list) 
     for child in el.getchildren(): 
      child_dicts[child.tag].append(xml_to_item(child)) 
     return collections.OrderedDict(child_dicts) or item 

    def xml_to_dict(el): 
     return {el.tag: xml_to_item(el)} 

    return xml_to_dict(root) 

x = simplexml_load_file('routines/test.xml') 
print(x) 

for y in x['root']: 
    print(y) 

La salida producida a partir de su archivo XML de prueba es el siguiente:

salida:

{'root': 
    OrderedDict(
     [('a', ['1']), 
     ('aa', [OrderedDict([('b', [OrderedDict([('c', ['2'])]), '2'])])]), 
     ('aaa', ['3']), 
     ('aaaa', [OrderedDict([('bb', ['4'])])]), 
     ('aaaaa', ['5']) 
     ] 
    ) 
} 

a 
aa 
aaa 
aaaa 
aaaaa 

lo que creo que está cerca de lo que desea.

* Si su versión de Python no tiene OrderedDict, que se introdujo en v2.5, puede utilizar la receta ActiveState de Ordered Dictionary for Py2.4 de Raymond Hettinger como una clase base.

actualización menor:

añadido un método __reduce__() que permitirá a las instancias de la clase a decapar y unpickled correctamente. Esto no fue necesario para esta pregunta, pero apareció en similar.

1

Hay muchos posible implementación de OrderedDict que figuran en la respuesta aquí: How do you retrieve items from a dictionary in the order that they're inserted?

usted puede crear su propio módulo OrderedDict para su uso en su propio código copiando uno de las implementaciones. Supongo que no tiene acceso al OrderedDict debido a la versión de Python que está ejecutando.

Un aspecto interesante de su pregunta es la posible necesidad de la funcionalidad defaultdict. Si necesita esto, puede implementar el método __missing__ para obtener el efecto deseado.

1

La receta de martineau funciona para mí, pero tiene problemas con el método copy() heredado de DefaultDict.El siguiente enfoque solucionar este inconveniente:

class OrderedDefaultDict(OrderedDict): 
    #Implementation as suggested by martineau 

    def copy(self): 
     return type(self)(self.default_factory, self) 

Por favor, considere que esta aplicación no hace deepcopy, que parece especialmente para los diccionarios predeterminados y no lo que hay que hacer en la mayoría de las circunstancias

Cuestiones relacionadas