17: Is

W tej lekcji podajemy szczegóły dotyczące sposobu przechowywania list w Pythonie. Kopiowanie i porównywanie list może mieć nieoczekiwane konsekwencje. Wyjaśnimy, jak Python działa wewnętrznie, abyś mógł zrozumieć i unikać tych błędów. Ogólnie rzecz biorąc, kilka różnych zmiennych może "wskazywać" lub "odwoływać się do" tej samej listy. Pod koniec lekcji opisujemy operator "is", który mówi, czy dwie zmienne naprawdę wskazują dokładnie tę samą listę.

Przykład

Załóżmy, że chcemy przekonwertować fragment kodu: listę długości określoną w calach o nazwie oldSize na listę o tej samej długości w centymetrach o nazwie newSize. Jednym ze sposobów, którym spróbujemy to zrobić, jest użycie wiersza newSize = oldSize do wykonania kopii, a następnie przejście przez wszystkie wartości w celu ich zmiany:

oldSize = ["letter", 8.5, 11]   # rozmiar papieru w calach
newSize = oldSize               # kopiowanie?
newSize[1] = newSize[1]*2.54    # zamiana na centymetry
newSize[2] = newSize[2]*2.54    #zamiana na centymetry
Teraz spójrzmy, czy to działa. Jeśli drukujesz newSize, to zgodnie z oczekiwaniami otrzymujemy ["letter", 21.59, 27.94]. Ważne jest to, że podczas drukowania oldSize zmieniły się wartości oldSize! Ilustrujemy to poniżej:

Przykład
Druga linia wyjścia nie jest tym, czego się spodziewamy!

Dokładnie wyjaśnimy, co się stało, używając diagramów. Głównym problemem jest to, że newSize = oldSize nie skopiował całej listy: po prostu skopiował odnośnik (strzałkę) do tej samej listy.

Klikaj tytuły, aby zmieniać zakładki.


Memory
Użyjemy tabeli do zaprezentowania zmiennych i wartości w pamięci Pythona. Na przykład po uruchomieniu kodu

city = "Moose Factory"
population = 2458
employer = city
tabela (z czarną ramką) pokazuje, jak wygląda pamięć Pythona:

Pierwsza nazwa zmiennej to city, a jej wartością jest ciąg "Moose Factory". Drugą nazwą zmiennej jest population, a jej wartością jest liczba całkowita 2458. Nazwa trzeciej zmiennej to employer, a jej wartością jest łańcuch "Moose Factory"
Listy w pamięci
Teraz pokażemy, jak wygląda lista w pamięci. Na przykład, pobierz fragment kodu

myList = ["Moose Factory", 2458]
Po prostu utworzymy jedną zmienną o nazwie myList. Lista została utworzona, a wartości myList "odwołują się" lub "wskazują" na tą listę. Reprezentujemy listę używając pola, a wartości listy są wyświetlane wewnątrz pola obok odpowiadających im indeksów. Lista jest wyświetlana na niebiesko.

Strzałka wskazuje, że myList odnosi się do tej nowej listy. Elementem o indeksie 0 to łańcuch "Moose Factory", a elementem o indeksie 1 to liczba całkowita 2458. Na przykład, jeśli wywołasz print(myList[1]), Python wyprowadzi 2458.
Zmiana wartości listy
Teraz, tylko dla celów ilustracyjnych, dodajemy jeszcze jedną linię do poprzedniego przykładu. Dziecko rodzi się, więc uruchamiamy fragment kodu

myList = ["Moose Factory", 2458]
myList[1] = myList[1]+1
Python oblicza 2458+1 co jest równe 2459, a to zastępuje wartość o indeksie 1 listy. Po aktualizacji mamy diagram przedstawiony poniżej.

(Przekreślone 2458 jest tylko po to widoczne, aby podkreślić zmianę.)
oldSize i newSize
Teraz wracamy do głównego przykładu. Pierwsza linia była

oldSize = ["letter", 8.5, 11]
a więc, po wykonaniu tej linii, pamięć Pythona wygląda jak diagram poniżej. Utworzyliśmy listę długości 3.
Główny problem
W naszym programie druga linia to

newSize = oldSize
A najważniejszy punkt tej lekcji jest tutaj: = nie duplikuje listy! Zamiast tego tworzy nowe odwołanie do tej samej listy. Ilustrują to dwie różne strzały wskazujące na dokładnie to samo pole.
Jak pokazuje wykres, mamy dwie zmienne, które odnoszą się do tej samej listy.
Uaktualnienie
Następnie, gdy program osiągnie linię

newSize[1] = newSize[1]*2.54
Python wyszukuje nowySize, wyszukuje wartość indeksu 1 (8.5) i mnoży go przez 2,54, a następnie, jak pokazano, zastępuje wartość. Ponieważ oldSize odwołuje się do tej samej listy, efektem ubocznym jest to, że wpływaliśmy także na to, do czego odwoływało się oldSize!

(Ponownie 8.5 pokazano tylko po to, aby podkreślić zmianę.)
Rezultat
Następna linia jest podobna,

newSize[2] = newSize[2]*2.54
co ma wpływ na inną wartość na liście. Po wykonaniu tej linii pamięć Pythona wygląda jak pokazano na diagramie poniżej.

Teraz, gdy drukujemy newSize o lub oldSize, Python wyprowadza ["letter", 21.59, 27.94].

Aby zobaczyć rysunki bez komentarza, uruchom ten sam kod w wizualizatorze.

Właściwie każda wartość w Pythonie jest obiektem żyjącym w konkretnym fragmencie pamięci, a każda zmienna jest tylko odnośnikiem/wskaźnikiem/strzałką. Na przykład, w pierwszym slajdzie pamięć zawiera jedną "Moose Factory" z prowadzącymi do niej dwiema strzałkami. Ponieważ liczba i obiekty łańcucha są "niezmienne", rysujemy je bez strzałek w tych lekcjach, ale one są za kulisami, Python traktuje wszystkie dane jednolicie. Czytaj więcej tutaj.

"Deep" Kopiowanie List w Pythonie

W powyższym przykładzie używamy = aby utworzyć drugi wskaźnik/odnośnik/strzałkę do istniejącej listy, która zwykle nazywa się "płytką" kopią. Chociaż, to jest czasami przydatne, to nie jest jednak to, czego chcieliśmy. Zamiast tego chcieliśmy stworzyć zupełnie nową kopię tej listy. Jak to zrobić?
Podamy trzy rozwiązania. Prawie wszystkie są równoważne, ale każde z nich nauczy cię nowego faktu o Pythonie, więc wszystkie trzy są warte przeczytania.

Metoda 1, użycie [:]

Przykład
Kopiowanie z użyciem [:]

Powyżej demonstrujemy, że newList = oldList[:] tworzy prawdziwą kopię starej listy. Choć ta składnia wygląda dziwnie, jest to podobne do tego, co już widzieliśmy. W lekcji o łańcuchach wprowadziliśmy sposób wyodrębniania podłańcucha: code>string[pierwszy:ostatni] zwraca łańcuch znaków wyjściowych zaczynając od indeksu pierwszy i kończąc na indeksie ostatni-1. Wspomnieliśmy, że może to mieć zastosowanie do tworzenia podlist list. Dodatkowo,

  • jeśli pominiesz pierwszy, wtedy przyjmuje domyślną wartość 0;
  • jeśli pominiesz ostatni, wtedy przyjmuje domyślną wartość len (długość listy/łańcucha).

Więc starsza oldList[:] tworzy nową podlistę, ale zawiera wszystkie te same dane, co oryginalna lista, więc jest to nowa kopia.

Metoda 2, użycie copy.copy()

Jest to znany moduł copy, który zawiera kilka metod kopiowania. Najprostszym jest copy.copy(L): gdy wywołujesz na liście L, funkcja zwraca prawdziwą kopię L.

Przykład
Kopiowanie z użyciem copy.copy

Funkcja copy.copy() działa również na inne rodzaje obiektów, ale nie będzie to tutaj omówione.

Metoda 3, użycie list()

Ostatnim sposobem na tworzenie prawdziwej kopii jest użycie funkcji list(). Zwykle list() służy do konwertowania danych z innych typów na typ listy. (Na przykład, list("hello") konwertuje konwertuje łańcuch "hello" na listę 5 elementów, gdzie każdy element ma jeden znak.) Jeśli jednak spróbujesz przekonwertować coś, co już jest listą na listę, wtedy otrzymasz kopię.

Przykład
Kopiowanie z użyciem list()

Listy jako Argumenty

Zauważ, że ze względu na sposób pracy list, każda funkcja akceptująca listę jako argument może zmienić zawartość tej listy. (Widziałeś to już w ćwiczeniu replace.)

Zadanie krótkiej odpowiedzi: Lista Argumentów
Jaką liczbę otrzymamy na wyjściu poniższego programu?

def func(list):
   list[0] = list[0] + list[1]
   list[1] = list[1] + list[0]
data = [3, 4]
func(data)
print(data[0]*data[1])
Prawidłowo! Wewnątrzfunc, zmieniamy element o indeksie 0 na 7 i element o indeksie 1 na 7+4=11. Więc, wydrukuje 7*11.

Porównanie list za pomocą is

Kiedy dwie listy zmiennych L1 i L2 są równe? Istnieją dwa różne sposoby interpretacji tego pytania:

  • Tym Samym(tożsame): Czy L1 i L2 wskazują/odnoszą się dokładnie do tego samego obiektu listy?
  • Te Same Wartości: Czy zawartość listy L1 jest równa zawartości listy L2?

Okazuje się, że w Pythonie standardowy operator równości == ma Te Same Wartości oznacza to, co pokazuje poniższy przykład.

Przykład
Znaczenie ==

Do sprawdzenia, czy są Tym Samym, Python używa operatora is. Używamy tego operatora w taki sam sposób jak ==; składnia

«list1» is «list2»

zwraca True jeśli listy odnoszą się do tej samej listy, a False jeśli odnoszą się do różnych list (nawet jeśli mają te same treści).

Przykład
Znaczenie is

Zadanie krótkiej odpowiedzi: Liczenie True
Ile razy zostanie wyświetlone True na wyjściu tego programu? (Narysuj diagram, aby łatwiej prześledzić.)

list1 = [9, 1, 1]
list3 = list(list1)
list2 = list1
list4 = list3[:]
list1[0] = 4
list5 = list1
print(list1 is list2, list2 is list3, list3 is list4, list4 is list5)
print(list1 == list2, list2 == list3, list3 == list4, list4 == list5)
Prawidłowo! Na wyjściu otrzymamy True False False False True False True False

Nie należy używać is razem z łańcuchami lub liczbami, ponieważ == poprawnie testuje równość, a zachowanie is jest trudne do przewidzenia na łańcuchach i liczbach.

Listy zagnieżdżone

Mamy już większość ważnych informacji, ale warto wspomnieć o jednej innej wspólnej sytuacji. Jak wspomniano w poprzedniej lekcji, zagnieżdżona lista to lista wewnątrz innej listy, na przykład

sample = [365.25, ["first", 5]]

Zewnętrzna lista, która odnosi się do sample, ma dwa elementy; Element o indeksie 0 to liczba float (liczba zmiennoprzecinkowa – reprezentacja liczby rzeczywistej zapisanej za pomocą notacji naukowej), a element na indeksie 1 jest listą wewnętrzną. Wewnętrzna lista to ["first", 5]. (Możesz też mieć więcej poziomów zagnieżdżania.) Kiedy rozpoczynasz korzystać z zagnieżdżonych list należy pamiętać o:

  • Zastosowanie trzech powyższych metod do sample spowoduje skopiowanie listy zewnętrznej, ale nie listy wewnętrznej. Zatem copy(sample)[1] is sample[1] oznacza to, że kopia nadal jest odniesieniem do części oryginalnej listy. Prawdopodobnie, to nie było to, czego chciałeś. Jeśli chcesz wykonać prawdziwą kopię na wszystkich poziomach, użyj copy.deepcopy().
  • Sprawdzanie zagnieżdżonych list z użyciem == jest bardzo intuicyjne: Python rekurencyjnie wywołuje == na każdym elemencie listy. Na przykład [[1, 2], 3]==[[1, 2], 3] jest True, a [[1, 2], 3]==[1, 2, 3] jest False, ponieważ pierwsze elementy Są różne ([1, 2] != 1).

Przykład
deepcopy i równość rekurencyjna

Zakończyłeś tę lekcję! Kolejny dodatkowy materiał jest opcjonalny.


Tuples: ("immutable", "lists")

Wspomnieliśmy powyżej, że gdy wywołasz funkcję na liście, może to zmienić listę. Czasami chcesz to uczynić niemożliwym! Rozwiązanie w Pythonie polega na tworzeniu tuple (krotek), które są takie same jak listy, z wyjątkiem tego, że nigdy nie można ich zmienić. Mówimy, że listy są "zmienne", a krotki są "niezmienne". (Ciągi i liczby są niezmienne). Może to być użyteczny sposób, aby zapobiec błędom programowania wynikajacym ze zmieny list, które powinny pozostać niezmienione. Tuple są prawie identyczne z listami, z tym wyjątkiem, że zamiast nawiasów kwadratowych [] używają okrągłych nawiasów (). Można dokonywać konwersji między krotkami i listami za pomocą tuple() i list().

Przykład
Tubular tuples

Do i Dalej: Samozatrzymanie

Można mieć listę, która zawiera samą siebie! Wystarczy utworzyć listę, a następnie tak przekierować jeden z jej elementów, aby wskazywał całą listę:

Przykład
A circular reference

Zauważ, że Pythona jest na tyle inteligentny, że może rozpoznać, że lista powraca do siebie: drukuje "...", zamiast drukować wszystkie L, aby uniknąć nieskończonej pętli.