2015-06-22 7 views
12

Nel mio codice ho spesso bisogno di prendere un sottoinsieme di chiavi + valori da un Python OrderedDict (dal pacchetto collections). Affettare non funziona (tiri TypeError: unhashable type) e l'alternativa, l'iterazione, è ingombrante:Slicing a Python OrderedDict

from collections import OrderedDict 

o = OrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 4)]) 

# want to do: 
# x = o[1:3] 
# need to do: 
x = OrderedDict() 
for idx, key in enumerate(o): 
    if 1 <= idx < 3: 
     x[key] = o[key] 

C'è un modo migliore per ottenere questo fatto?

risposta

6

Il dict ordinata nella libreria standard, non prevede che la funzionalità. Anche se le librerie esistevano per alcuni anni prima di collections.OrderedDict che hanno questa funzionalità (e forniscono essenzialmente un superset di OrderedDict): voidspace odict e ruamel.ordereddict (sono l'autore di quest'ultimo pacchetto, che è una reimplementazione di odict in C):

from odict import OrderedDict as odict 
p = odict([('a', 1), ('b', 2), ('c', 3), ('d', 4)]) 
print p[1:3] 

In ruamel.ordereddict ci si può rilassare il requisito d'ingresso ordinata (per quanto ne so non si può chiedere derivato del dict se le sue chiavi sono ordinate (sarebbe buona aggiunta al ruamel.ordereddict riconoscere collection.OrderedDicts)):

from ruamel.ordereddict import ordereddict 

q = ordereddict(o, relax=True) 
print q[1:3] 
r = odict([('a', 1), ('b', 2), ('c', 3), ('d', 4)]) 
print r[1:3] 

Se si desidera (o si deve) rimanere all'interno dello standa biblioteca Rd si può sublass collections.OrderedDict s' __getitem__:

class SlicableOrderedDict(OrderedDict): 
    def __getitem__(self, k): 
     if not isinstance(k, slice): 
      return OrderedDict.__getitem__(self, k) 
     x = SlicableOrderedDict() 
     for idx, key in enumerate(self.keys()): 
      if k.start <= idx < k.stop: 
       x[key] = self[key] 
     return x 

s = SlicableOrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 4)]) 
print s[1:3] 

naturalmente è possibile utilizzare versioni più corte di Martijn o Jimmy per ottenere la fetta reale che ha bisogno di tornare:

from itertools import islice 
class SlicableOrderedDict(OrderedDict): 
    def __getitem__(self, k): 
     if not isinstance(k, slice): 
      return OrderedDict.__getitem__(self, k) 
     return SlicableOrderedDict(islice(self.viewitems(), k.start, k.stop)) 

t = SlicableOrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 4)]) 
print t[1:3] 

o se volete semplicemente abbellire tutte le esistenti OrderedDict s senza subclassing:

def get_item(self, k): 
    if not isinstance(k, slice): 
     return OrderedDict._old__getitem__(self, k) 
    return OrderedDict(islice(self.viewitems(), k.start, k.stop)) 

OrderedDict._old__getitem__ = OrderedDict.__getitem__ 
OrderedDict.__getitem__ = get_item 

u = OrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 4)]) 
print u[1:3] 
5

In Python 2, è possibile tagliare le chiavi :

x.keys()[1:3] 

e di supportare sia Python 2 e Python 3, che ci si converte a una lista prima:

list(k)[1:3] 

Il L'implementazione di Python 2 OrderedDict.keys() fa esattamente questo.

In entrambi i casi viene fornito un elenco di chiavi nell'ordine corretto. Se la creazione di un intero elenco prima è un problema, è possibile utilizzare itertools.islice() e convertire l'iterabile che produce a un elenco:

from itertools import islice 

list(islice(x, 1, 3)) 

Tutto quanto sopra può anche essere applicato alle voci; utilizzare dict.viewitems() in Python 2 per ottenere lo stesso comportamento di iterazione di Python 3 dict.items(). È possibile passare il rettilineo islice() oggetto ad un altro OrderedDict() in questo caso:

OrderedDict(islice(x.items(), 1, 3)) # x.viewitems() in Python 2 
9

È possibile utilizzare la funzione di itertools.islice, che prende un iterabile e restituisce i primi elementi stop. Ciò è utile poiché i iterabili non supportano il metodo di divisione comune e non sarà necessario creare l'intero elenco items da OrderedDict.

from collections import OrderedDict 
from itertools import islice 
o = OrderedDict([('a', 1), ('b', 2), ('c', 3), ('d', 4)]) 
sliced = islice(o.iteritems(), 3) # o.iteritems() is o.items() in Python 3 
sliced_o = OrderedDict(sliced) 
+0

È questo O (n)? sembra che questa operazione non debba essere. –

0

ho voluto tagliare con una chiave, dal momento che non sapevo l'indice in anticipo:

o = OrderedDict(zip(list('abcdefghijklmnopqrstuvwxyz'),range(1,27))) 

stop = o.keys().index('e')   # -> 4 
OrderedDict(islice(o.items(),stop)) # -> OrderedDict([('a', 1), ('b', 2), ('c', 3)]) 

o per tagliare start-stop:

start = o.keys().index('c')     # -> 2 
stop = o.keys().index('e')      # -> 4 
OrderedDict(islice(o.iteritems(),start,stop)) # -> OrderedDict([('c', 3), ('d', 4)]) 
0
def slice_odict(odict, start=None, end=None): 
    return OrderedDict([ 
     (k,v) for (k,v) in odict.items() 
     if k in list(odict.keys())[start:end] 
    ]) 

Ciò consente:

>>> x = OrderedDict([('a',1), ('b',2), ('c',3), ('d',4)]) 
>>> slice_odict(x, start=-1) 
OrderedDict([('d', 4)]) 
>>> slice_odict(x, end=-1) 
OrderedDict([('a', 1), ('b', 2), ('c', 3)]) 
>>> slice_odict(x, start=1, end=3) 
OrderedDict([('b', 2), ('c', 3)])