2010-11-08 10 views
11

Ho la seguente funzione che fa un lavoro approssimativo di analisi di un file XML in un dizionario.Come può essere riscritta questa funzione per implementare OrderedDict?

Sfortunatamente, poiché i dizionari Python non sono ordinati, non riesco a scorrere i nodi come vorrei.

Come si modifica questo in modo che emetta un dizionario ordinato che riflette l'ordine originale dei nodi quando viene eseguito il ciclo 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 

Uscite:

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

a 
aa 
aaaa 
aaa 
aaaaa 

Come posso implementare collections.OrderedDict in modo che possa essere sicuri di ottenere il corretto ordine dei nodi?

file XML per riferimento:

<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

duplicato di http: // StackOverflow. it/questions/4123266/python-looping-seem-to-not-follow-sequence dello stesso autore. –

risposta

27

È possibile utilizzare la nuova sottoclasse OrderedDictdict che è stata aggiunta al modulo collections della libreria standard nella versione 2.7 *. In realtà quello che vi serve è una combinazione Ordered + defaultdict che non esiste, ma è possibile creare una sottoclasse OrderedDict come illustrato di seguito:

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) 

L'output prodotto dal file XML di test è simile al seguente:

uscita:

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

a 
aa 
aaa 
aaaa 
aaaaa 

che credo è vicino a ciò che si desidera.

* Se la tua versione di Python non ha OrderedDict, che è stato introdotto in v2.5, potresti utilizzare la ricetta di ActiveState di Raymond Hettinger Ordered Dictionary for Py2.4 come una classe base.

Minor update:

Aggiunto un metodo __reduce__() che permetterà le istanze della classe da decapato e correttamente deserializzato. Questo non era necessario per questa domanda, ma è arrivato a similar uno.

1

Ci sono molte possibili attuazione OrderedDict elencati nella risposta qui: How do you retrieve items from a dictionary in the order that they're inserted?

è possibile creare il proprio modulo OrderedDict per l'uso nel proprio codice copiando uno delle implementazioni. Presumo che tu non abbia accesso a OrderedDict a causa della versione di Python che stai utilizzando.

Un aspetto interessante della tua domanda è l'eventuale necessità di funzionalità defaultdict. Se necessario, è possibile implementare il metodo __missing__ per ottenere l'effetto desiderato.

1

La ricetta di martineau funziona per me, ma ha problemi con il metodo copy() ereditato da DefaultDict.Il seguente approccio risolvere questo inconveniente:

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

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

perche, che questa implementazione non fa deepcopy, che sembra soprattutto per i dizionari predefiniti, piuttosto la cosa giusta da fare nella maggior parte dei casi

Problemi correlati