Dlaczego potrzebujemy krotek w Pythonie (lub dowolnego niezmiennego typu danych)?

140

Przeczytałem kilka samouczków dotyczących języka Python (na przykład Dive Into Python) i odniesienie do języka na Python.org - nie rozumiem, dlaczego język wymaga krotek.

Krotki nie mają metod w porównaniu z listą lub zbiorem, a jeśli muszę przekonwertować krotkę na zestaw lub listę, aby móc je sortować, jaki jest sens używania krotki w pierwszej kolejności?

Niezmienność?

Dlaczego kogoś obchodzi, czy zmienna żyje w innym miejscu w pamięci niż wtedy, gdy została pierwotnie przydzielona? Wydaje się, że cała ta niezmienność w Pythonie jest zbyt mocno podkreślana.

W C / C ++, jeśli przydzielę wskaźnik i wskażę jakąś prawidłową pamięć, nie obchodzi mnie, gdzie znajduje się adres, o ile nie jest pusty, zanim go użyję.

Ilekroć odwołuję się do tej zmiennej, nie muszę wiedzieć, czy wskaźnik nadal wskazuje oryginalny adres, czy nie. Po prostu sprawdzam, czy nie ma null i używam go (lub nie).

W Pythonie, kiedy przydzielam ciąg (lub krotkę), przypisuję go do x, a następnie modyfikuję ciąg, dlaczego obchodzi mnie, czy to oryginalny obiekt? Dopóki zmienna wskazuje na moje dane, to wszystko się liczy.

>>> x='hello'
>>> id(x)
1234567
>>> x='good bye'
>>> id(x)
5432167

x nadal odwołuje się do danych, które chcę, dlaczego ktoś musi się przejmować, czy jego identyfikator jest taki sam czy inny?

pyNewGuy
źródło
12
zwracasz uwagę na zły aspekt zmienności: „czy identyfikator jest taki sam czy inny” to tylko efekt uboczny; „czy dane wskazywane przez inne odniesienia, które wcześniej wskazywały na ten sam obiekt, teraz odzwierciedlają aktualizacje” jest krytyczne.
Charles Duffy

Odpowiedzi:

124
  1. niezmienne obiekty mogą pozwolić na znaczną optymalizację; przypuszczalnie jest to powód, dla którego ciągi znaków są również niezmienne w Javie, tworzone całkiem osobno, ale mniej więcej w tym samym czasie co Python, i prawie wszystko jest niezmienne w prawdziwie funkcjonalnych językach.

  2. w szczególności w Pythonie tylko niezmienne mogą być hashowalne (a zatem elementy zestawów lub klucze w słownikach). Ponownie, pozwala to na optymalizację, ale znacznie więcej niż tylko „znaczne” (projektowanie przyzwoitych tablic mieszających przechowujących całkowicie zmienne obiekty jest koszmarem - albo bierzesz kopie wszystkiego zaraz po zaszyfrowaniu, albo koszmar sprawdzania, czy hash obiektu zmienił się od czasu, gdy ostatnio odwołałeś się do tego, że podnosi swoją brzydką głowę).

Przykład problemu z optymalizacją:

$ python -mtimeit '["fee", "fie", "fo", "fum"]'
1000000 loops, best of 3: 0.432 usec per loop
$ python -mtimeit '("fee", "fie", "fo", "fum")'
10000000 loops, best of 3: 0.0563 usec per loop
Alex Martelli
źródło
11
@musicfreak, zobacz edycję, którą właśnie zrobiłem, w której tworzenie krotki jest ponad 7,6 razy szybsze niż tworzenie równoważnej listy - teraz nie możesz już powiedzieć, że „nigdy nie widziałeś zauważalnej różnicy”, chyba że Twoja definicja „zauważalnej” „jest naprawdę osobliwy ...
Alex Martelli
11
@musicfreak Myślę, że nadużywasz „przedwczesna optymalizacja jest źródłem wszelkiego zła”. Istnieje ogromna różnica między wykonywaniem przedwczesnej optymalizacji w aplikacji (na przykład stwierdzeniem „krotki są szybsze niż listy, więc zamierzamy używać tylko krotek w całej aplikacji!”) A testami porównawczymi. Benchmark Alexa jest wnikliwy i świadomość, że tworzenie krotki jest szybsze niż tworzenie listy, może nam pomóc w przyszłych operacjach optymalizacyjnych (kiedy jest to naprawdę potrzebne).
Virgil Dupras
5
@Alex, czy „budowanie” krotki jest naprawdę szybsze niż „budowanie listy”, czy też widzimy wynik buforowania krotki przez środowisko wykonawcze Pythona? Wydaje mi się, że to drugie.
Tryptyk
6
@ACoolie, to jest całkowicie zdominowane przez randomwywołania (spróbuj to zrobić, zobaczysz!), Więc nie jest zbyt znaczące. Spróbuj, python -mtimeit -s "x=23" "[x,x]"a zobaczysz bardziej znaczące przyspieszenie 2-3 razy przy tworzeniu krotki w porównaniu z budowaniem listy.
Alex Martelli
9
dla każdego, kto się zastanawia - udało nam się zaoszczędzić ponad godzinę przetwarzania danych, przełączając się z list na krotki.
Mark Ribau,
42

Żadna z powyższych odpowiedzi nie wskazuje na rzeczywisty problem krotek i list, których wielu nowych w Pythonie wydaje się nie w pełni rozumieć.

Krotki i listy służą różnym celom. Listy przechowują jednorodne dane. Możesz i powinieneś mieć taką listę:

["Bob", "Joe", "John", "Sam"]

Powodem, dla którego jest to prawidłowe użycie list, jest to, że są to wszystkie jednorodne typy danych, a konkretnie nazwiska osób. Ale weź listę taką jak ta:

["Billy", "Bob", "Joe", 42]

Ta lista zawiera pełne imię i nazwisko osoby oraz jej wiek. To nie jest jeden rodzaj danych. Prawidłowym sposobem przechowywania tych informacji jest krotka lub obiekt. Powiedzmy, że mamy kilka:

[("Billy", "Bob", "Joe", 42), ("Robert", "", "Smith", 31)]

Niezmienność i zmienność krotek i list nie jest główną różnicą. Lista to lista elementów tego samego rodzaju: plików, nazw, obiektów. Krotki to grupa różnych typów obiektów. Mają różne zastosowania i wiele list nadużyć koderów Pythona dotyczących krotek.

Proszę nie.


Edytować:

Myślę, że ten post na blogu wyjaśnia, dlaczego uważam to lepiej niż ja: http://news.e-scribe.com/397

Udziel Paulowi
źródło
13
Myślę, że masz wizję, której przynajmniej ja nie zgadzam, nie znasz innych.
Stefano Borini
13
Ja też zdecydowanie nie zgadzam się z tą odpowiedzią. Jednorodność danych nie ma absolutnie nic wspólnego z tym, czy powinieneś używać listy, czy krotki. Nic w Pythonie nie sugeruje takiego rozróżnienia.
Glenn Maynard
14
Guido zwrócił na to uwagę kilka lat temu. aspn.activestate.com/ASPN/Mail/Message/python-list/1566320
John La Rooy
11
Mimo że Guido (projektant Pythona) chciał, aby listy były używane w przypadku danych jednorodnych, a krotki w przypadku danych heterogenicznych, faktem jest, że język tego nie wymusza. Dlatego uważam, że ta interpretacja jest bardziej kwestią stylu niż czegokolwiek innego. Tak się składa, że ​​w typowych przypadkach użycia wielu ludzi listy mają tendencję do przypominania tablic, a krotki przypominają rekordy. Nie powinno to jednak powstrzymywać ludzi przed używaniem list do heterogenicznych danych, jeśli lepiej odpowiada to ich problemowi. Jak mówi Zen of Python: Praktyczność jest ważniejsza od czystości.
John Y
9
@Glenn, zasadniczo się mylisz. Jednym z głównych zastosowań krotek jest złożenie typu danych do przechowywania wielu powiązanych ze sobą danych. Fakt, że można iterować po krotce i wykonywać wiele takich samych operacji, nie zmienia tego. (Jako odniesienie weź pod uwagę, że krotki w wielu innych językach nie mają tych samych iterowalnych funkcji, co ich odpowiedniki w postaci list)
HS.
22

Jeśli muszę przekonwertować krotkę na zestaw lub listę, aby móc je posortować, jaki jest sens używania krotki w pierwszej kolejności?

W tym konkretnym przypadku prawdopodobnie nie ma sensu. To nie jest problem, ponieważ nie jest to jeden z przypadków, w których rozważasz użycie krotki.

Jak zauważyłeś, krotki są niezmienne. Powody posiadania niezmiennych typów dotyczą krotek:

  • efektywność kopiowania: zamiast kopiować niezmienny obiekt, możesz go aliasować (powiązać zmienną z referencją)
  • wydajność porównania: gdy używasz kopiowania przez odniesienie, możesz porównać dwie zmienne, porównując lokalizację, a nie zawartość
  • internowanie: musisz przechowywać co najwyżej jedną kopię dowolnej niezmiennej wartości
  • nie ma potrzeby synchronizowania dostępu do niezmiennych obiektów w kodzie współbieżnym
  • const poprawność: niektóre wartości nie powinny ulec zmianie. To (dla mnie) jest głównym powodem niezmiennych typów.

Zwróć uwagę, że konkretna implementacja Pythona może nie wykorzystywać wszystkich powyższych funkcji.

Klucze słownika muszą być niezmienne, w przeciwnym razie zmiana właściwości obiektu-klucza może unieważnić niezmienniki podstawowej struktury danych. W ten sposób krotki mogą być potencjalnie używane jako klucze. Jest to konsekwencja stałej poprawności.

Zobacz także „ Wprowadzenie krotek ” z Dive Into Python .

poza
źródło
2
id ((1,2,3)) == id ((1,2,3)) jest fałszem. Nie można porównywać krotek po prostu porównując lokalizację, ponieważ nie ma gwarancji, że zostały skopiowane przez odniesienie.
Glenn Maynard
@Glenn: Zwróć uwagę na uwagę kwalifikującą „kiedy używasz kopiowania przez odniesienie”. Podczas gdy programista może stworzyć własną implementację, kopiowanie przez odniesienie dla krotek jest w dużej mierze sprawą interpretera / kompilatora. Chodziło mi głównie o to, jak ==jest wdrażany na poziomie platformy.
wyjazd
1
@Glenn: pamiętaj również, że kopiowanie przez odniesienie nie ma zastosowania do krotek w (1,2,3) == (1,2,3). To bardziej kwestia stażu.
outis
Jak powiedziałem dość wyraźnie, nie ma gwarancji, że zostały skopiowane przez odniesienie . Krotki nie są internowane w Pythonie; to jest koncepcja strun.
Glenn Maynard
Tak jak powiedziałem bardzo wyraźnie: nie mówię o programatorze porównującym krotki przez porównywanie lokalizacji. Mówię o możliwości, jaką oferuje platforma, co gwarantuje kopiowanie przez odniesienie. Ponadto internowanie można zastosować do dowolnego niezmiennego typu, nie tylko do ciągów. Główna implementacja Pythona może nie uwzględniać niezmiennych typów, ale fakt, że Python ma niezmienne typy, sprawia, że ​​internowanie jest opcją.
outis
15

Czasami lubimy używać obiektów jako kluczy słownika

Na ile to jest warte, ostatnio wzrosły krotki (2.6+) index()i count()metody

John La Rooy
źródło
5
+1: Zmienna lista (lub zmienny zestaw lub zmienny słownik) jako klucz słownika nie może działać. Potrzebujemy więc niezmiennych list („krotek”), zamrożonych zestawów i… cóż… zamrożonego słownika, jak sądzę.
S.Lott
9

Zawsze uważałem, że posiadanie dwóch całkowicie oddzielnych typów dla tej samej podstawowej struktury danych (tablic) jest niezręcznym projektem, ale nie stanowi prawdziwego problemu w praktyce. (Każdy język ma swoje brodawki, w tym Python, ale to nie jest ważne).

Dlaczego kogoś obchodzi, czy zmienna żyje w innym miejscu w pamięci niż wtedy, gdy została pierwotnie przydzielona? Wydaje się, że cała ta niezmienność w Pythonie jest zbyt mocno podkreślana.

To są różne rzeczy. Zmienność nie jest związana z miejscem, w którym jest przechowywana w pamięci; oznacza to, że rzeczy, na które wskazuje, nie mogą się zmienić.

Obiekty Pythona nie mogą zmieniać lokalizacji po ich utworzeniu, są modyfikowalne lub nie. (Dokładniej, wartość id () nie może się zmienić - to samo w praktyce.) Wewnętrzna pamięć obiektów podlegających zmianom może się zmienić, ale to jest ukryty szczegół implementacji.

>>> x='hello'
>>> id(x)
1234567
>>> x='good bye'
>>> id(x)
5432167

To nie jest modyfikacja („mutacja”) zmiennej; tworzy nową zmienną o tej samej nazwie i odrzuca starą. Porównaj z operacją mutacji:

>>> a = [1,2,3]
>>> id(a)
3084599212L
>>> a[1] = 5
>>> a
[1, 5, 3]
>>> id(a)
3084599212L

Jak zauważyli inni, pozwala to na używanie tablic jako kluczy do słowników i innych struktur danych, które wymagają niezmienności.

Pamiętaj, że klucze do słowników nie muszą być całkowicie niezmienne. Tylko jego część używana jako klucz musi być niezmienna; w przypadku niektórych zastosowań jest to ważna różnica. Na przykład możesz mieć klasę reprezentującą użytkownika, która porównuje równość i skrót według unikalnej nazwy użytkownika. Możesz wtedy zawiesić inne zmienne dane w klasie - „użytkownik jest zalogowany” itp. Ponieważ nie ma to wpływu na równość ani na hash, jest możliwe i całkowicie poprawne użycie tego jako klucza w słowniku. Nie jest to zbyt często potrzebne w Pythonie; Zwracam na to uwagę, ponieważ kilka osób twierdziło, że klucze muszą być „niezmienne”, co jest tylko częściowo poprawne. Jednak używałem tego wiele razy z mapami i zestawami C ++.

Glenn Maynard
źródło
>>> a = [1,2,3] >>> id (a) 3084599212L >>> a [1] = 5 >>> a [1, 5, 3] >>> id (a) 3084599212L Ty ' Właśnie zmodyfikowaliśmy zmienny typ danych, więc nie ma to sensu w odniesieniu do pierwotnego pytania. x = 'cześć "id (x) 12345 x =" Do widzenia "id (x) 65432 Kogo obchodzi, czy to nowy obiekt, czy nie. Jeśli x wskazuje na przypisane przeze mnie dane, to wszystko się liczy.
pyNewGuy
4
Jesteś zdezorientowany, daleko poza moją zdolnością do pomocy.
Glenn Maynard
+1 za wskazanie nieporozumień w pytaniach podrzędnych, które wydają się być głównym źródłem trudności w postrzeganiu wartości krotek.
outis
1
Gdybym mógł, kolejne +1 za wskazanie, że prawdziwym kryterium dla kluczy jest to, czy obiekt jest hashable ( docs.python.org/glossary.html#term-hashable ).
wyjście z
7

Jak podał w komentarzu gnibbler, Guido miał opinię, która nie jest w pełni akceptowana / doceniana: „listy są dla danych jednorodnych, krotki są dla danych heterogenicznych”. Oczywiście wielu przeciwników zinterpretowało to w ten sposób, że wszystkie elementy listy powinny być tego samego typu.

Lubię to widzieć inaczej, podobnie jak inni też w przeszłości:

blue= 0, 0, 255
alist= ["red", "green", blue]

Zauważ, że uważam alist za jednorodny, nawet jeśli typ (alist [1])! = Typ (alist [2]).

Jeśli mogę zmienić kolejność elementów i nie będę miał problemów w swoim kodzie (poza założeniami np. „Należy posortować”) to należy skorzystać z listy. Jeśli nie (jak w krotce bluepowyżej), powinienem użyć krotki.

tzot
źródło
Gdybym mógł, zagłosowałbym na tę odpowiedź 15 razy. Dokładnie tak myślę o krotkach.
Grant Paul
6

Są ważne, ponieważ gwarantują wywołującemu, że obiekt, który przekazują, nie zostanie zmutowany. Jeśli to zrobisz:

a = [1,1,1]
doWork(a)

Dzwoniący nie ma gwarancji wartości a po wywołaniu. Jednak,

a = (1,1,1)
doWorK(a)

Teraz ty jako dzwoniący lub jako czytelnik tego kodu wiesz, że a jest tym samym. W tym scenariuszu zawsze możesz zrobić kopię listy i przekazać ją, ale teraz marnujesz cykle zamiast używać konstrukcji językowej, która ma większy sens semantyczny.

Matthew Manela
źródło
1
Jest to bardzo drugorzędna właściwość krotek. Jest zbyt wiele przypadków, w których masz zmienny obiekt, który chcesz przekazać do funkcji i nie chcesz go zmodyfikować, niezależnie od tego, czy jest to wcześniejsza lista, czy inna klasa. W Pythonie nie ma po prostu pojęcia „stałych parametrów przez odniesienie” (np. Const foo i w C ++). Zdarza się, że krotki dają ci to, jeśli w ogóle wygodnie jest użyć krotki, ale jeśli otrzymałeś listę od dzwoniącego, czy naprawdę zamierzasz przekonwertować ją na krotkę przed przekazaniem jej gdzie indziej?
Glenn Maynard
Zgadzam się w tym z tobą. Krotka to nie to samo, co uderzenie w słowo kluczowe const. Chodzi mi o to, że niezmienność krotki niesie dodatkowe znaczenie dla czytelnika kodu. Biorąc pod uwagę sytuację, w której oba zadziałałyby, a twoje oczekiwanie jest takie, że nie powinno się to zmienić, użycie krotki doda tego dodatkowego znaczenia dla czytelnika (zapewniając to również)
Matthew Manela,
a = [1,1,1] doWork (a) jeśli dowork () jest zdefiniowane jako def dowork (arg): arg = [0,0,0] wywołanie dowork () na liście lub krotce daje ten sam wynik
pyNewGuy
1

możesz zobaczyć tutaj dyskusję na ten temat

ghostdog74
źródło
1

Twoje pytanie (i dalsze komentarze) skupiają się na tym, czy id () zmienia się podczas zadania. Skoncentrowanie się na tym następczym efekcie różnicy między niezmiennym zastępowaniem obiektu a modyfikacją obiektu zmiennego zamiast samej różnicy nie jest być może najlepszym podejściem.

Zanim przejdziemy dalej, upewnij się, że zachowanie przedstawione poniżej jest takie, jakiego oczekujesz od Pythona.

>>> a1 = [1]
>>> a2 = a1
>>> print a2[0]
1
>>> a1[0] = 2
>>> print a2[0]
2

W tym przypadku zawartość a2 została zmieniona, mimo że tylko a1 miała przypisaną nową wartość. Porównaj z poniższym:

>>> a1 = (1,)
>>> a2 = a1
>>> print a2[0]
1
>>> a1 = (2,)
>>> print a2[0]
1

W tym drugim przypadku zastąpiliśmy całą listę, zamiast aktualizować jej zawartość. W przypadku niezmiennych typów, takich jak krotki, jest to jedyne dozwolone zachowanie.

Dlaczego to ma znaczenie? Powiedzmy, że masz dyktando:

>>> t1 = (1,2)
>>> d1 = { t1 : 'three' }
>>> print d1
{(1,2): 'three'}
>>> t1[0] = 0  ## results in a TypeError, as tuples cannot be modified
>>> t1 = (2,3) ## creates a new tuple, does not modify the old one
>>> print d1   ## as seen here, the dict is still intact
{(1,2): 'three'}

Używając krotki, słownik jest bezpieczny przed zmienianiem kluczy „spod niego” na elementy, które mają inną wartość. Ma to kluczowe znaczenie dla skutecznego wdrożenia.

Charles Duffy
źródło
Jak zauważyli inni, niezmienność! = Hashability. Nie wszystkie krotki mogą być używane jako klucze słownika: {([1], [2]): 'wartość'} kończy się niepowodzeniem, ponieważ zmienne listy w krotce mogą być zmienione, ale {((1), (2)): ' value '} jest OK.
Ned Deily
Ned, to prawda, ale nie jestem pewien, czy to rozróżnienie ma związek z zadawanym pytaniem.
Charles Duffy
@ K.Nicholas, zmiana, którą tutaj zatwierdziłeś, zmieniła kod w taki sposób, że w ogóle przypisywano liczbę całkowitą, a nie krotkę - co spowodowało, że późniejsze operacje indeksowania zawiodły, więc prawdopodobnie nie mogli przetestować, że nowy transkrypcja była faktycznie możliwa. Jasne, poprawnie zidentyfikowany problem; nieprawidłowe rozwiązanie.
Charles Duffy,
@MichaelPuckettII, podobnie, patrz wyżej.
Charles Duffy,