Dlaczego słowniki Pythona nie są odwracalne dla Python3.7?

11

Począwszy od wersji 3.7, standardowe słowniki Pythona gwarantują utrzymanie kolejności wstawiania. (*)

d = {'b': 1, 'a': 2}
for k in d: 
    print(k)
# Prints always 'b' before 'a'.

Innymi słowy, klucze dict są przechowywane w ścisłej kolejności. Zasadniczo pozwoliłoby to na odwrócenie kluczy. Żadne z poniższych działań nie działa:

# TypeError: 'dict' object is not reversible
for k in reversed(d): 
    print(k)

# TypeError: 'dict_keys' object is not reversible
for k in reversed(d.keys()): 
    print(k)

Pytania: Jakie jest uzasadnienie tego zachowania? Dlaczego dyktatury nie stały się odwracalne? Czy są jakieś plany zmiany tego zachowania w przyszłości?

Obejście oczywiście działa:

for k in reversed(list(d.keys())): 
    print(k)

(*) W rzeczywistości dotyczy to już typowych instalacji Pythona 3.6, jak omówiono w tym poście .


Aktualizacja : Począwszy od słowników Python 3.8 są w rzeczywistości odwracalne. Przyjęta odpowiedź odnosi się do dyskusji między Guido i innymi głównymi programistami, która doprowadziła do tej decyzji. W skrócie, porównali spójność językową z wysiłkami wdrożeniowymi i rzeczywistymi korzyściami dla użytkowników.

normanius
źródło

Odpowiedzi:

5

Z dokumentów :

odwrócony ( seq )

Zwróć odwrotną stronę iterator. seq musi być obiektem, który ma __reversed__()metodę lub obsługuje protokół sekwencji ( __len__()metoda i __getitem__()metoda z argumentami liczb całkowitych rozpoczynającymi się od 0).

dictObiekt nie realizuje __reversed__. Implementuje obie te ostatnie metody. Jednak __getitem__bierze klucze jako argumenty, zamiast liczb całkowitych (rozpoczynając od 0).

Dlaczego zostało to już zasugerowane i omówione tutaj .

EDYTOWAĆ:

Te cytaty pochodzą z listy mailingowej Python-Dev (wątek „Add __reversed__ metody for dict”, zapoczątkowany 25. 05. 18), zacznę od argumentów „koncepcyjnych”, pierwszy z nich to Antoine Pitrou:

Nie jest nic warte, że OrDERDict już obsługuje odwrócone (). Argument może przebiegać w dwie strony:

  1. dict jest obecnie podobny do OrdersDict, dlatego też powinien obsługiwać także reverse ();

  2. możesz użyć OragedDict, aby jednoznacznie zasygnalizować, że zależy Ci na zamawianiu; nie trzeba nic dodawać do dyktowania.

Uważam, że gwarantowana kolejność wstawiania zwykłych nagrań jest zupełnie nowa, więc przyjmie chwilę, zanim pojęcie się pojawi i stanie się częścią codziennego myślenia o nagraniach. Gdy to nastąpi, prawdopodobnie nieuniknione będzie pojawienie się przypadków użycia i dodanie __reversed__ w pewnym momencie. Implementacja wydaje się prosta i nie jest wielkim skokiem koncepcyjnym, aby oczekiwać, że kolekcja o skończonej kolejności będzie odwracalna.

Po odpowiedzi Raymond Hettinger:

Biorąc pod uwagę, że dykty śledzą teraz kolejność wstawiania, rozsądne wydaje się chcieć poznać najnowsze wstawienia (tj. Zapętlanie ostatnio dodanych zadań w dykcie zadań). Inne możliwe przypadki użycia będą prawdopodobnie odpowiadały sposobowi używania komendy tail w systemie Unix.

Jeśli pojawią się te przypadki użycia, dobrze byłoby, gdyby obsługa __reversed__ była już obsługiwana, aby ludzie nie mieli ochoty wdrożyć brzydkiego obejścia za pomocą wywołań popitem (), a następnie ponownych wstawień.

Główną obawą wyrażoną na liście mailingowej było to, że spowodowałoby to nadmierne wzdęcie lub zmniejszyłoby wydajność pamięci (konieczność posiadania podwójnie powiązanych list zamiast pojedynczo połączonych) w co najmniej niektórych implementacjach, oto cytat Inady Naoki z narzędzia do śledzenia błędów w Pythonie ( problem 33462 ):

„Zamów” nie oznacza „odwracalnego”. Na przykład pojedyncza połączona lista jest uporządkowana, ale nieodwracalna.

Chociaż implementacja CPython może zapewnić efektywność __reverse__, dodawanie __reverse__oznacza, że oczekuje się od niej wszystkich implementacji Python. Na przykład niektóre implementacje Pythona mogą implementować dict za pomocą hashmap + pojedynczej listy połączonej. Po __reverse__dodaniu nie jest to już możliwe.

Wracając do listy mailingowej, oto dwie ostatnie wiadomości (obie opublikowane 08.06.2018). Pierwszy pochodzi od Michaela Selika:

Czy mam rację twierdząc, że konsensus wynosi +1 dla włączenia do wersji v3.8?

Ostatnim punktem tego wątku była INADA Naoki badająca różne implementacje i decydująca, że ​​włączenie tej funkcji w wersji 3.8 jest w porządku. Jak rozumiem, Guido zgadzał się z radą INADA, aby poczekać na wdrożenie MicroPython w wersji 3.7. Odkąd INADA zmieniła zdanie, zgaduję, że to wszystko na korzyść?

Na zakończenie przesłanie Guido van Rossuma:

To mi pasuje. Będziemy wtedy mieć dwie wersje, w których tak było:

  • 3.6, gdzie zachowano porządek w CPython, ale w specyfikacji języka

  • 3.7, gdzie został również dodany do specyfikacji języka

Jak zauważono w innej odpowiedzi i komentarzach, reversed()jest on obsługiwany zarówno dla nagrań, jak i przeglądów nagrań od wersji 3.8 (14.10.2018).

gst
źródło
5
Twój drugi cytat wydaje się być wyborem nastawienia z tego wątku. Wydaje się, że istnieje konsensus, że funkcjonalność zostanie dodana w wersji 3.8. Również przed Pythonem 3.7 po prostu nie było porządku na normalnym dictobiekcie (przynajmniej nie gwarantowanym z języka), więc reversedteż nie miało sensu
FlyingTeller
Nie mam psa do walki, właśnie zacytowałem jego pierwszą odpowiedź. Ale masz rację, uważaj dobrze - usunąłem cytat.
gst
1
Dzięki. Wątek dyskusji w python-dev był odkrywczy. W rzeczywistości funkcja ta została już zaimplementowana w Pythonie 3.8, który został wydany zaledwie dwa dni temu (14 października 2019 r.).
normanius
Cytat z dokumentacji nie jest pomocny, tylko nasuwa kolejne pytanie „więc dlaczego typ dyktu nie implementuje __reversed__”? Link do python-dev ma przydatną treść, ale odpowiednie części powinny być powielone w odpowiedzi bezpośrednio (ponieważ takie linki zewnętrzne mają tendencję do gnicia).
wim
1

Aktualizacja dla Pythona 3.8

Dict i dictviews są teraz iterowalne w odwróconej kolejności wstawiania za pomocą reverse ()

>>> dict = {1: "1", 2: "2", 3: "3"}
>>> reversed(dict)
<dict_reversekeyiterator object at 0x7f72ca795130>
Гончаров Дмитрий
źródło
Czy ta odpowiedź wnosi coś nowego, co nie jest jeszcze objęte innymi odpowiedziami, komentarzami lub samym pytaniem?
normanius
@normanius Ma przykładowy kod wizualny lil, może być pomocny w szybkiej przeglądarce
jamylak