2015-06-22 19 views
12

In meinem Code muss ich häufig einen Teilbereich der Schlüssel + Werte aus einem Python (aus collections Paket) nehmen. Slicing nicht funktioniert (wirft TypeError: unhashable type) und die Alternative, Iterieren, ist umständlich:Slicing ein 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] 

Gibt es einen besseren Weg, dies zu tun bekommen?

Antwort

6

Die geordnete dict in der Standard-Bibliothek, nicht diese Funktionalität bieten. Obwohl Bibliotheken existierten für ein paar Jahre, bevor collections.OrderedDict, die diese Funktionalität (und bieten im Wesentlichen eine Ober von OrderedDict): voidspace odict und ruamel.ordereddict (ich bin der Autor des letzteren Pakets, das eine Neuimplementierung von 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 können Sie die bestellten Eingangs Anforderung entspannen (AFAIK nicht Ableitung von dict fragen, ob seine Schlüssel bestellt werden (würde gute Ergänzung ruamel.ordereddict collection.OrderedDicts) zu erkennen):

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] 

Wenn Sie (oder müssen) innerhalb der standa bleiben wollen rd Bibliothek können Sie collections.OrderedDict ‚s __getitem__ sublass:

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] 

natürlich Sie Martijn oder des Jimmys kürzere Versionen verwenden könnte die tatsächliche Scheibe zu erhalten, die Rückkehr braucht:

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] 

oder wenn Sie nur wollen, sich in Schale werfen alle vorhandenen OrderedDict s ohne 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, können Sie die Tasten schneiden:

x.keys()[1:3] 

und unterstützen sowohl Python 2 und Python 3, Sie in eine Liste konvertieren würde zuerst:

list(k)[1:3] 

Die Python 2 OrderedDict.keys() Implementierung tut genau das.

In beiden Fällen erhalten Sie eine Liste der Schlüssel in der richtigen Reihenfolge. Wenn die Erstellung zunächst eine ganze Liste ein Problem ist, können Sie itertools.islice() verwenden und wandeln die iterable es auf eine Liste erzeugt:

from itertools import islice 

list(islice(x, 1, 3)) 

Alle oben genannten können auch auf die Elemente angewendet werden; Verwenden Sie in Python 2 dict.viewitems(), um das gleiche Iterationsverhalten wie Python 3 dict.items() zu erhalten. Sie können das islice() Objekt direkt zu einem anderen OrderedDict() in diesem Fall passieren:

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

können Sie die itertools.islice Funktion verwenden, die nimmt ein iterabler und gibt die stop ersten Elemente aus. Dies ist vorteilhaft, da iterable die allgemeine Slicing-Methode nicht unterstützt und Sie nicht die gesamte items-Liste aus OrderedDict erstellen müssen.

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

Ist das O (n)? es scheint, als ob diese Operation nicht sein muss. –

0

Ich wollte einen Schlüssel schneiden verwenden, da ich nicht den Index im Voraus wusste:

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)]) 

oder in Scheiben schneiden 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] 
    ]) 

Dies ermöglicht:

>>> 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)])