Różnica polega na tym, że reversed
jest iteratorem (jest także leniwą wyceną), a sorted
to funkcja, która działa "z zapałem".
Wszystkie wbudowane iteratory (przynajmniej w python-3.x) jak map
, zip
, filter
, reversed
... są implementowane jako klas. Podczas gdy wbudowane moduły są bardzo przydatne, są to: funkcje, np. min
, max
, any
, all
i sorted
.
>>> a = [1,2,3,4]
>>> r = reversed(a)
<list_reverseiterator at 0x2187afa0240>
rzeczywiście trzeba „konsumować” iteracyjnej w celu uzyskania wartości (np list
):
>>> list(r)
[4, 3, 2, 1]
Z drugiej strony ta „zużywa” część nie jest potrzebny do funkcji jak sorted
:
>>> s = sorted(a)
[1, 2, 3, 4]
I W komentarzach zadano , dlaczego są one implementowane jako klasy zamiast funkcji. Odpowiedź na to pytanie nie jest łatwa, ale postaram się jak najlepiej:
Korzystanie z operacji leniwej oceny ma jedną ogromną zaletę: są one bardzo wydajne pod względem pamięci, gdy są powiązane.Nie muszą tworzyć pośrednich list, chyba że są wyraźnie "wymagane". Z tego powodu zmieniono map
,i filter
z gorliwie działających funkcji (python-2.x) na leniwy klasy operacyjne (python-3.x).
Generalnie istnieją dwa sposoby, w Pythonie, aby utworzyć iteratorów:
- klas, które
return self
w ich sposobie __iter__
- generator funkcji - Funkcje, które zawierają
yield
natomiast (przynajmniej CPython) implementuje wszystkie wbudowane (i kilka standardowych modułów bibliotecznych) w C. Tworzenie klas iteratora w C jest bardzo łatwe, ale nie znalazłem sensownego sposobu tworzenia funkcji generatora jony oparte na C-API Pythona. Zatem powodem, dla którego te iteratory są implementowane jako klasy (w CPython) może być po prostu wygoda lub brak (szybkich lub możliwych do wdrożenia) alternatyw.
Istnieje dodatkowy powód, aby używać klas zamiast generatorów: Można implementować specjalne metody dla klas, ale nie można ich implementować w funkcjach generatora. To może nie brzmi imponująco, ale ma określone zalety. Na przykład większość iteratorów może być pickled (przynajmniej na Python-3.x) przy użyciu metod __reduce__
i __setstate__
. Oznacza to, że można je przechowywać na dysku i umożliwia ich kopiowanie. Od wersji Python-3.4 niektóre iteratory również implementują __length_hint__
, co sprawia, że konsumowanie tych iteratorów za pomocą list
(i podobnych) jest znacznie szybsze.
Zauważ, że reversed
może być łatwo zaimplementowane jako fabrycznych funkcji (jak iter
), ale w przeciwieństwie do iter
, która może powrócić dwa unikalne klas reversed
może jedynie powrócić jeden unikalny klasę.
Aby zilustrować możliwe (i wyjątkowe) zajęcia trzeba wziąć pod uwagę klasę, która nie ma __iter__
i nie __reversed__
metody, ale są iterable i reverse-iterable (poprzez wdrożenie __getitem__
i __len__
):
class A(object):
def __init__(self, vals):
self.vals = vals
def __len__(self):
return len(self.vals)
def __getitem__(self, idx):
return self.vals[idx]
I gdy ma to sens, aby dodać warstwę abstrakcji (funkcja fabryczne) w przypadku iter
- bo wrócił klasa jest w zależności od liczby argumentów wejściowych:
>>> iter(A([1,2,3]))
<iterator at 0x2187afaed68>
>>> iter(min, 0) # actually this is a useless example, just here to see what it returns
<callable_iterator at 0x1333879bdd8>
To rozumowanie nie ma zastosowania do reversed
:
>>> reversed(A([1,2,3]))
<reversed at 0x2187afaec50>
Są różne, ponieważ robią różne rzeczy. Wynik końcowy może być postrzegany jako podobny, ale droga do osiągnięcia tego wyniku jest zupełnie inna. –
Niewyjaśnione spekulacje: 'odwrócony' jest typem, ponieważ zwraca wartość iterowalną, która implementuje metodę [' __length_hint__'] (https://docs.python.org/3/reference/datamodel.html#object.__length_hint__). Ponieważ ta metoda musi być zdefiniowana w klasie, ma ona sens dla samego "odwróconego", aby była tą klasą. (Problem z tą teorią polega na tym, że '__length_hint__' istnieje tylko od python 3.4) –
Pozwólcie, że zadam odwrotne pytanie: dlaczego oczekujecie, że będą tacy sami? – jpmc26