itertools.groupby
dokumentacja mówi, że
itertools.groupby(iterable, key=None)
[...]
Działanie groupby()
jest podobny do filtra uniq w systemie Unix. Generuje przerwę lub nową grupę za każdym razem, gdy zmienia się wartość funkcji klucza (dlatego zazwyczaj konieczne jest posortowanie danych przy użyciu tej samej funkcji klawiszy). To zachowanie różni się od GROUP BY grupy SQL, która agreguje wspólne elementy, niezależnie od ich kolejności wprowadzania.
Zwrócona grupa sama jest iteratorem, który dzieli obiekt bazowy z wartością iteracji przy pomocy groupby()
. Ponieważ źródło jest współużytkowane, gdy obiekt `groupby() jest zaawansowany, poprzednia grupa nie jest już widoczna. Tak więc, jeśli dane są potrzebne później, to powinny być przechowywane w postaci listy [-]
Więc założenie z ostatnim akapicie jest to, że wygenerowany lista będzie pusta lista []
, ponieważ iterator już się rozwinął i poznał StopIteration
; ale zamiast tego w CPython wynik jest zaskakujący: [9]
.
To dlatego, że _grouper
iterator pozostaje jedną pozycję za oryginalnym iterator, który jest dlatego groupby
potrzeby zaglądać jedną pozycję do przodu, aby zobaczyć, czy należy on do prądu lub następnej grupy, ale to musi być w stanie później wydaj ten element jako pierwszy element nowej grupy.
Jednak currkey
i currvalue
atrybuty groupby
są nie zresetowane gdy original iterator is exhausted, więc currvalue
nadal wskazuje na ostatniej pozycji z iteracyjnej.
Dokumentacja CPython faktycznie zawiera ten równoważny kod, który ma również dokładnie takie samo zachowanie jak kod wersji C:
class groupby:
# [k for k, g in groupby('AAAABBBCCDAABBB')] --> A B C D A B
# [list(g) for k, g in groupby('AAAABBBCCD')] --> AAAA BBB CC D
def __init__(self, iterable, key=None):
if key is None:
key = lambda x: x
self.keyfunc = key
self.it = iter(iterable)
self.tgtkey = self.currkey = self.currvalue = object()
def __iter__(self):
return self
def __next__(self):
while self.currkey == self.tgtkey:
self.currvalue = next(self.it) # Exit on StopIteration
self.currkey = self.keyfunc(self.currvalue)
self.tgtkey = self.currkey
return (self.currkey, self._grouper(self.tgtkey))
def _grouper(self, tgtkey):
while self.currkey == tgtkey:
yield self.currvalue
try:
self.currvalue = next(self.it)
except StopIteration:
return
self.currkey = self.keyfunc(self.currvalue)
szczególności tablice __next__
odnajduje pierwszy element z następnej grupy i zapisuje to jego klucz do self.currkey
i jego wartość do self.currvalue
. Ale kluczem jest linia
self.currvalue = next(self.it) # Exit on StopIteration
Kiedy next
rzuca StopItertion
self.currvalue
nadal zawiera ostatni klucz poprzedniej grupy. Teraz, gdy y[1]
zostanie przekształcony w list
, to najpierw najpierw daje wartość self.currvalue
, a następnie uruchamia next()
na podstawowym iteratorze (i znów spotyka się z StopIteration
).
Chociaż istnieje Python równoważne w dokumentacji, że zachowuje się dokładnie tak autorytatywnego realizacji kodu C w CPython, IronPython, Jython i pypy dają różne wyniki.