Dlaczego w Pythonie nie ma zrozumienia krotek?

340

Jak wszyscy wiemy, istnieje zrozumienie listy

[i for i in [1, 2, 3, 4]]

i istnieje zrozumienie słownika

{i:j for i, j in {1: 'a', 2: 'b'}.items()}

ale

(i for i in (1, 2, 3))

skończy w generatorze, a nie w tuplezrozumieniu. Dlaczego?

Domyślam się, że a tuplejest niezmienne, ale nie wydaje się, że to jest odpowiedź.

Shady Xu
źródło
15
Istnieje również kompletne rozumienie - które wygląda bardzo podobnie do rozumienia dict ...
mgilson
3
W kodzie jest błąd składni: {i:j for i,j in {1:'a', 2:'b'}}powinien być{i:j for i,j in {1:'a', 2:'b'}.items()}
Inbar Rose
@InbarRose Dzięki za wskazanie go -.-
Shady Xu
Ze względu na potomstwo, dyskusja na ten temat odbywa się na czacie
Inbar Rose
Najwyraźniej jest. stackoverflow.com/a/51811147/9627166
Super S

Odpowiedzi:

471

Możesz użyć wyrażenia generatora:

tuple(i for i in (1, 2, 3))

ale nawiasy zostały już pobrane dla… wyrażeń generatora.

Martijn Pieters
źródło
15
Ten argument, moglibyśmy powiedzieć, lista-zrozumienie jest konieczne zbyt: list(i for i in (1,2,3)). Naprawdę myślę, że to po prostu dlatego, że nie ma dla niego czystej składni (lub przynajmniej nikt o tym nie pomyślał)
mgilson,
79
Zrozumienie listy, zbioru lub dykta to po prostu cukier składniowy, aby użyć wyrażenia generatora, które generuje określony typ. list(i for i in (1, 2, 3))jest wyrażeniem generatora, które wypisuje listę, set(i for i in (1, 2, 3))wypisuje zestaw. Czy to oznacza, że ​​składnia zrozumienia nie jest potrzebna? Może nie, ale jest to okropnie przydatne. W rzadkich przypadkach potrzebna jest krotka. Wyrażenie generatora wystarczy, jest jasne i nie wymaga wynalezienia innego nawiasu klamrowego lub nawiasu.
Martijn Pieters
16
Odpowiedź jest oczywista, ponieważ składnia krotek i nawiasy są niejednoznaczne
Charles Salvia
19
Różnica między używaniem zrozumienia a używaniem konstruktora + generatora jest bardziej niż subtelna, jeśli zależy Ci na wydajności. Wyjaśnienia skutkują szybszą budową w porównaniu do korzystania z generatora przekazanego do konstruktora. W tym drugim przypadku tworzysz i wykonujesz funkcje, a funkcje są drogie w Pythonie. [thing for thing in things]konstruuje listę znacznie szybciej niż list(thing for thing in things). Zrozumienie krotki nie byłoby bezużyteczne; tuple(thing for thing in things)ma problemy z opóźnieniami i tuple([thing for thing in things])może mieć problemy z pamięcią.
Justin Turner Arthur
9
@MartijnPieters, Potrafisz przeredagować A list or set or dict comprehension is just syntactic sugar to use a generator expression? Powoduje zamieszanie przez ludzi postrzegających je jako równoważne środki do celu. Nie jest to technicznie syntaktyczny cukier, ponieważ procesy są w rzeczywistości różne, nawet jeśli produkt końcowy jest taki sam.
jpp
77

Raymond Hettinger (jeden z programistów rdzenia Pythona) powiedział o krotkach w ostatnim tweecie :

#python tip: Zasadniczo listy służą do zapętlania; krotki dla struktur. Listy są jednorodne; krotki niejednorodne. Listy dla zmiennej długości.

To (dla mnie) popiera ideę, że jeśli elementy w sekwencji są wystarczająco powiązane, aby mogły zostać wygenerowane przez, cóż, generator, to powinna to być lista. Chociaż krotka jest iterowalna i wydaje się po prostu niezmienną listą, jest tak naprawdę odpowiednikiem struktury C w Pythonie:

struct {
    int a;
    char b;
    float c;
} foo;

struct foo x = { 3, 'g', 5.9 };

staje się w Pythonie

x = (3, 'g', 5.9)
chepner
źródło
26
Właściwość niezmienności może być jednak ważna i często stanowi dobry powód, aby użyć krotki, gdy zwykle używasz listy. Na przykład, jeśli masz listę 5 liczb, których chcesz użyć jako klucza do nagrania, to krotka jest najlepszym rozwiązaniem.
pavon
To ładna wskazówka od Raymonda Hettingera. Nadal powiedziałbym, że istnieje przypadek użycia konstruktora krotki z generatorem, taki jak rozpakowanie innej struktury, być może większej, na mniejszą przez iterację attrów, które są zainteresowane konwersją na rekord krotki.
dave
2
@dave Prawdopodobnie możesz po prostu użyć operator.itemgetterw takim przypadku.
chepner
@chepner, rozumiem. To bardzo blisko tego, co mam na myśli. Zwraca możliwość wywołania, więc jeśli muszę to zrobić tylko wtedy, gdy nie widzę dużej wygranej w porównaniu z tuple(obj[item] for item in items)bezpośrednim użyciem . W moim przypadku umieściłem to w liście, aby utworzyć listę krotek. Jeśli muszę to powtarzać w całym kodzie, itemgetter wygląda świetnie. Być może tak czy inaczej itemgetter byłby bardziej idiomatyczny?
dave
Widzę związek między zamrożonym a zestawem analogicznym do związku krotki i listy. Nie chodzi tu o heterogeniczność, a bardziej o niezmienność - frozensety i krotki mogą być kluczami do słowników, list i zestawów, ponieważ nie są zmienne.
polyglot
56

Od wersji Python 3.5 możesz także użyć *składni rozpakowywania splat do rozpakowania wyrażenia generatora:

*(x for x in range(10)),
czheo
źródło
2
To jest świetne (i działa), ale nie mogę znaleźć nigdzie to udokumentowane! Czy masz link?
felixphew
8
Uwaga: Jako szczegół implementacji jest to w zasadzie to samo co robienie tuple(list(x for x in range(10)))( ścieżki kodu są identyczne , przy czym oba budują a list, z tą jedyną różnicą, że ostatnim krokiem jest utworzenie wartości tuplez listi wyrzucenie, listkiedy tuplewyjście jest wymagane). Oznacza, że ​​tak naprawdę nie unikasz tymczasowości.
ShadowRanger
4
Aby rozwinąć komentarz @ShadowRanger, oto pytanie, w którym pokazują, że dosłowna składnia splat + krotka jest w rzeczywistości nieco wolniejsza niż przekazywanie wyrażenia generatora do konstruktora krotki.
Lucubrator
Próbuję tego w Pythonie 3.7.3 i *(x for x in range(10))nie działa. I dostać SyntaxError: can't use starred expression here. Jednak tuple(x for x in range(10))działa.
Ryan H.
4
@RyanH. na końcu musisz wstawić przecinek.
czheo
27

Jak macmwspomniano w innym plakacie , najszybszym sposobem na stworzenie krotki z generatora jest tuple([generator]).


Porównanie wydajności

  • Zrozumienie listy:

    $ python3 -m timeit "a = [i for i in range(1000)]"
    10000 loops, best of 3: 27.4 usec per loop
  • Próbuj ze zrozumienia listy:

    $ python3 -m timeit "a = tuple([i for i in range(1000)])"
    10000 loops, best of 3: 30.2 usec per loop
  • Krotka z generatora:

    $ python3 -m timeit "a = tuple(i for i in range(1000))"
    10000 loops, best of 3: 50.4 usec per loop
  • Krotka z rozpakowywania:

    $ python3 -m timeit "a = *(i for i in range(1000)),"
    10000 loops, best of 3: 52.7 usec per loop

Moja wersja Pythona :

$ python3 --version
Python 3.6.3

Dlatego zawsze powinieneś utworzyć krotkę na podstawie listy, chyba że wydajność nie stanowi problemu.

Tomek
źródło
10
Uwaga: tuplez listcomp wymaga maksymalnego użycia pamięci w oparciu o łączny rozmiar pliku końcowego tuplei list. tuplegenexpr, choć wolniejszy, oznacza, że ​​płacisz tylko za końcowy tuple, a nie tymczasowy list(sam genexpr zajmujący mniej więcej ustaloną pamięć). Zwykle nie ma to znaczenia, ale może być ważne, gdy rozmiary są ogromne.
ShadowRanger
25

Zrozumienie polega na zapętlaniu lub iterowaniu elementów i przypisywaniu ich do kontenera, a Tuple nie może odbierać zadań.

Po utworzeniu krotki nie można jej dodawać, rozszerzać ani przypisywać. Jedynym sposobem zmodyfikowania krotki jest to, że jeden z jej obiektów może być przypisany (jest to kontener niebędący krotką). Ponieważ krotek zawiera tylko odniesienie do tego rodzaju obiektu.

Ponadto krotka ma własnego konstruktora, tuple()który można nadać dowolnemu iteratorowi. Co oznacza, że ​​aby utworzyć krotkę, możesz:

tuple(i for i in (1,2,3))
Inbar Rose
źródło
9
Pod pewnymi względami zgadzam się (co do tego, że lista nie będzie konieczna), ale pod innymi względami nie zgadzam się (co do tego, że rozumowanie jest niezmienne). W pewnym sensie bardziej sensowne jest zrozumienie niezmiennych obiektów. kto robi lst = [x for x in ...]; x.append()?
mgilson
@mgilson Nie jestem pewien, jak to się ma do tego, co powiedziałem?
Inbar Rose
2
@ mgilson, jeśli krotka jest niezmienna, oznacza to, że podstawowa implementacja nie może „wygenerować” krotki („generacja” oznacza zbudowanie jednego elementu na raz). niezmienny oznacza, że ​​nie możesz zbudować tego z 4 elementami, zmieniając ten z 3 elementami. zamiast tego implementujesz krotki „generowanie”, budując listę, coś zaprojektowanego do generowania, a następnie buduj krotkę jako ostatni krok i odrzucasz listę. Język odzwierciedla tę rzeczywistość. Pomyśl o krotkach jako strukturach C.
Scott
2
chociaż rozsądne byłoby, aby syntaktyczny cukier wyrażeń działał dla krotek, ponieważ nie można użyć krotki, dopóki nie zostanie zwrócone zrozumienie. W rzeczywistości nie działa jak zmienny, raczej zrozumienie krotki może zachowywać się podobnie jak dołączanie łańcucha.
uchuugaka
12

Domyślam się, że zabrakło im nawiasów i nie sądziłem, że przydałoby się dodać „brzydką” składnię…

mgilson
źródło
1
Nawiasy kątowe nieużywane.
uchuugaka
@uchuugaka - Nie do końca. Służą do porównania operatorów. Prawdopodobnie można to zrobić bez dwuznaczności, ale może nie jest to warte wysiłku ...
mgilson,
3
@uchuugaka Warto zauważyć, że {*()}choć brzydki, działa jak pusty zestaw literału!
MI Wright,
1
Ugh. Z estetycznego punktu widzenia myślę, że jestem set()
stronniczy
1
@QuantumMechanic: Tak, o to chodzi; rozpakowanie uogólnień umożliwiło pusty „zestaw dosłowny”. Zauważ, że {*[]}jest to zdecydowanie gorsze od innych opcji; pusty ciąg i pusty tuple, ponieważ są niezmienne, są singletonami, więc nie potrzeba tymczasowego skonstruowania pustego set. W przeciwieństwie do tego, puste listnie jest singletonem, więc musisz go zbudować, użyć go do zbudowania set, a następnie zniszczyć, tracąc jakąkolwiek trywialną przewagę wydajności, jaką zapewnia jednooki operator małpy.
ShadowRanger
8

Krotki nie mogą być skutecznie dołączane jak lista.

Tak więc zrozumienie krotki będzie wymagało użycia listy wewnętrznie, a następnie konwersji do krotki.

To byłoby to samo, co teraz robisz: krotka ([zrozumienie])

macm
źródło
3

Nawiasy nie tworzą krotki. aka jeden = (dwa) nie jest krotką. Jedynym rozwiązaniem jest jeden = (dwa) lub jeden = krotka (dwa). Tak więc rozwiązaniem jest:

tuple(i for i in myothertupleorlistordict) 
ilias iliadis
źródło
miły. jest prawie taki sam.
uchuugaka
-1

Uważam, że to po prostu ze względu na przejrzystość, nie chcemy zaśmiecać języka zbyt wieloma różnymi symbolami. Również tuplezrozumienie nigdy nie jest konieczne , zamiast tego można użyć listy z nieznacznymi różnicami prędkości, w przeciwieństwie do rozumienia dykta, w przeciwieństwie do rozumienia listy.

jamylak
źródło
-2

Możemy wygenerować krotki na podstawie listy. Poniższy dodaje kolejno dwie liczby do krotki i podaje listę od cyfr 0-9.

>>> print k
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99]
>>> r= [tuple(k[i:i+2]) for i in xrange(10) if not i%2]
>>> print r
[(0, 1), (2, 3), (4, 5), (6, 7), (8, 9)]
Rohit Malgaonkar
źródło