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 centymetryTeraz 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:
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.
city = "Moose Factory" population = 2458 employer = citytabela (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"
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
.myList = ["Moose Factory", 2458] myList[1] = myList[1]+1Python 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
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.
newSize = oldSizeA 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.
newSize[1] = newSize[1]*2.54Python 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ę oldSiz
e!(Ponownie 8.5 pokazano tylko po to, aby podkreślić zmianę.)
newSize[2] = newSize[2]*2.54co 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 [:]
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
.
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ę.
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
.)
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])
func
, 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
iL2
wskazują/odnoszą się dokładnie do tego samego obiektu listy? - Te Same Wartości: Czy zawartość listy
L1
jest równa zawartości listyL2
?
Okazuje się, że w Pythonie standardowy operator równości ==
ma Te Same Wartości oznacza to, co pokazuje poniższy przykład.
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).
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)
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. Zatemcopy(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żyjcopy.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]
jestTrue
, a[[1, 2], 3]==[1, 2, 3]
jestFalse
, ponieważ pierwsze elementy Są różne ([1, 2] != 1
).
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()
.
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ę:
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.