2012-04-03 9 views
19

Zmieniłem kod, który używał listy do użycia maski. Nie mogę już tego zrobić, ponieważ dostaję błąd:Jak wyciąć deskę?

TypeError: sequence index must be integer, not 'slice'

Oto REPL, który pokazuje problem.

>>> import collections 
>>> d = collections.deque() 
>>> for i in range(3): 
...  d.append(i) 
... 
>>> d 
deque([0, 1, 2]) 
>>> d[2:] 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
TypeError: sequence index must be integer, not 'slice' 

Tak, czy istnieje obejście wspierać krojenie w deques w Pythonie?

+0

@HunterMcMillen, dziękuję za komentarz. Używam 'deque' dla wydajności (w rzeczywistości jest to bufor cykliczny), więc kopiowanie danych do listy każdego cyklu nie jest idealne. –

+0

rel: http: // stackoverflow.com/questions/7064289/use-slice-notation-with-collections-deque – georg

+0

@ thg435, dzięki za odniesienie. Przeszukałem ciąg znaków błędu i nie znalazłem nic na SO, dlatego zamieszczam to nowe pytanie. Tam też dobre spostrzeżenia. –

Odpowiedz

23

Wypróbuj itertools.islice().

deque_slice = collections.deque(itertools.islice(my_deque, 10, 20)) 

indeksowania w deque wymaga następujących połączonej listy od początku za każdym razem, więc podejście islice(), pomijając elementy, aby dostać się na początku wycinka, daje najlepszą możliwą wydajność (lepsze niż kodowanie go jako operację indeksowania dla każdego elementu).

Możesz łatwo napisać podklasę deque, która robi to automagicznie dla ciebie.

class sliceable_deque(collections.deque): 
    def __getitem__(self, index): 
     if isinstance(index, slice): 
      return type(self)(itertools.islice(self, index.start, 
               index.stop, index.step)) 
     return collections.deque.__getitem__(self, index) 

Należy pamiętać, że nie można używać negatywnych wskaźników lub wartości kroku islice. Możliwe jest kodowanie wokół tego i może być opłacalne, jeśli podejmiesz podejście do podklasy. Dla negatywnego startu lub zatrzymania możesz po prostu dodać długość deque; aby uzyskać krok ujemny, musisz gdzieś tam rzucić reversed(). Zostawię to jako ćwiczenie. :-)

Wydajność pobierania pojedynczych przedmiotów z deque zostanie nieznacznie zmniejszona testem if dla plastra. Jeśli jest to problem, można użyć wzoru EAFP aby złagodzić ten nieco - kosztem podejmowania segmencie ścieżki nieco mniej wydajnych ze względu na konieczność przetworzyć wyjątek:

class sliceable_deque(collections.deque): 
    def __getitem__(self, index): 
     try: 
      return collections.deque.__getitem__(self, index) 
     except TypeError: 
      return type(self)(itertools.islice(self, index.start, 
               index.stop, index.step)) 

Oczywiście istnieje dodatkowa wywołanie funkcji nadal istnieje, w porównaniu do zwykłego deque, więc jeśli naprawdę zależy Ci na wydajności, na pewno chcesz dodać oddzielną metodę slice() lub tym podobne.

+4

Jestem ciągle zaskoczony, jak magiczny jest moduł itertools. Wydaje się, że coraz więcej się o tym uczę za każdym razem, gdy recenzuję SO. – jdi

+4

Moje duże moduły do ​​przechodzenia do klienta to itertools, functools i collections. Tyle mocy. – kindall

+0

Dzięki, korzystałem z indeksowania negatywnego na buforze o zmiennej długości, ale była to stała długość na instancję. Więc odwróciłem logikę i zrobiłem jej indeksowanie na instancję. Kompromis, który był więcej niż sprytny. – RobotHumans

8

Jeśli wydajność jest problemem, należy rozważyć metodę bezpośredniego dostępu/zrozumienia, zgodnie z sugestią podaną w dokumencie this answer. To znacznie szybciej niż islice na dużych zbiorach:

import timeit 

setup = """ 
import collections, itertools 
d = collections.deque(range(10000)) 
""" 

print timeit.timeit('list(itertools.islice(d, 9000, 9010))', setup, number=10000) 
## 0.631947040558 
print timeit.timeit('[d[i] for i in range(9000, 9010)]', setup, number=10000) 
## 0.0292208194733 

Zgodnie komentarz @RaymondHettinger poniżej, sposób pojmowania jest tylko lepiej, gdy plastry są krótkie. Na dłuższych plasterkach przekonująco wygrywa islice. Na przykład, oto czasy dla krojenie 10,000 pozycji deque z przesunięciem 6000:

 
offset length  islice  compr 
6000  10  400.496  46.611 
6000  50  424.600  183.988 
6000  90  432.277  237.894 
6000  130  441.289  352.383 
6000  170  431.299  404.596 
6000  210  456.405  546.503 
6000  250  448.895  575.995 
6000  290  485.802  778.294 
6000  330  483.704  781.703 
6000  370  490.904  948.501 
6000  410  500.011  875.807 
6000  450  508.213 1045.299 
6000  490  518.894 1010.203 
6000  530  530.887 1192.784 
6000  570  534.415 1151.013 
6000  610  530.887 1504.779 
6000  650  539.279 1486.802 
6000  690  536.084 1650.810 
6000  730  549.698 1454.687 
6000  770  564.909 1576.114 
6000  810  545.001 1588.297 
6000  850  564.504 1711.607 
6000  890  584.197 1760.793 
6000  930  564.480 1963.091 
6000  970  586.390 1955.199 
6000 1010  590.706 2117.491 

Zrozumienie robi pierwsze kilka plasterków bardzo szybko, ale wydajność spada dramatycznie, jak długość rośnie. islice jest wolniejszy na mniejszych plasterkach, ale jego średnia prędkość jest znacznie lepsza.

ten sposób testowałem:

import timeit 

size = 10000 
repeats = 100 

setup = """ 
import collections, itertools 
d = collections.deque(range(%d)) 
""" % size 

print '%5s\t%5s\t%10s\t%10s' % ('offset', 'length', 'islice', 'compr') 

for offset in range(0, size - 2000, 2000): 
    for length in range(10, 2000, 40): 
     t1 = timeit.timeit('list(itertools.islice(d, %d, %d))' % (offset, offset + length), setup, number=repeats) 
     t2 = timeit.timeit('[d[i] for i in range(%d, %d)]' % (offset, offset + length), setup, number=repeats) 
     print '%5d\t%5d\t%10.3f\t%10.3f' % (offset, length, t1 * 100000, t2 * 100000) 
+0

Oczywiście można to również zawrzeć w nadpisanej metodzie '__getitem__'. – aaronasterling

+0

To naprawdę zaskakujący wynik. – kindall

+4

Ta odpowiedź jest * naprawdę * wprowadzająca w błąd i wyciąga błędne wnioski z harmonogramu. Indeksowanie za pomocą d [i] jest na ogół szybkie, ponieważ wyszukiwania przechodzą w momencie 62 elementów i ponieważ są wystarczająco inteligentne, aby przechodzić z prawej strony, gdy '' i> len (d) // 2''. Jeśli jednak potrzebujesz dłuższego wycinka, indeksowanie pojedynczo jest złym pomysłem. Na przykład, otrzymasz znacznie inną odpowiedź, jeśli spróbujesz tego taktowania dla fragmentu '' 9000: 10000'' w postaci maski zawierającej 30000 elementów. –