O ile wiem, zmienne w Pythonie to tylko wskaźniki.
Na podstawie tej reguły mogę założyć, że wynik dla tego fragmentu kodu:
i = 5
j = i
j = 3
print(i)
byłoby 3
. Ale uzyskałem dla mnie nieoczekiwany wynik, to było 5
.
Co więcej, moja książka o Pythonie obejmuje ten przykład:
i = [1,2,3]
j = i
i[0] = 5
print(j)
wynik byłby [5,2,3]
.
Co źle rozumiem?
i
powinno być równe3
Odpowiedzi:
Nazywamy je referencjami. Działają w ten sposób
i = 5 # create int(5) instance, bind it to i j = i # bind j to the same int as i j = 3 # create int(3) instance, bind it to j print i # i still bound to the int(5), j bound to the int(3)
Małe int są internowane, ale to nie jest ważne dla tego wyjaśnienia
i = [1,2,3] # create the list instance, and bind it to i j = i # bind j to the same list as i i[0] = 5 # change the first item of i print j # j is still bound to the same list as i
źródło
40
, odwołujesz się do tego samego obiektu w pamięci. Aby zobaczyć ten typ a, b = 256 i przetestuja is b
. Teraz spróbuj z a, b = 257. Zobacz: stackoverflow.com/a/1136852/3973834 i codementor.io/python/tutorial/…Zmienne nie są wskaźnikami. Kiedy przypisujesz do zmiennej, wiążesz nazwę z obiektem. Od tego momentu możesz odwoływać się do obiektu używając nazwy, aż ta nazwa zostanie ponownie powiązana.
W pierwszym przykładzie nazwa
i
jest powiązana z wartością5
. Powiązanie różnych wartości z nazwąj
nie ma żadnego wpływu na toi
, więc podczas późniejszego drukowania wartośći
tej wartości jest nadal5
.W swoim drugim przykładzie powiązać oba
i
ij
do samej listy obiektów. Kiedy modyfikujesz zawartość listy, możesz zobaczyć zmianę niezależnie od tego, jakiej nazwy używasz w odniesieniu do listy.Zauważ, że byłoby niepoprawne, gdybyś powiedział „obie listy uległy zmianie”. Jest tylko jedna lista, ale ma dwie nazwy (
i
ij
), które się do niej odnoszą.Powiązana dokumentacja
źródło
Zmienne Pythona to nazwy powiązane z obiektami
Z dokumentów :
Kiedy to zrobisz
i = 5 j = i
to jest to samo co robienie:
i = 5 j = 5
j
nie wskazujei
, a po przypisaniuj
nie wie, żei
istnieje.j
jest po prostu związany z tym, na coi
wskazywał w momencie przydziału.Jeśli wykonałeś zadania w tej samej linii, wyglądałoby to tak:
i = j = 5
Rezultat byłby dokładnie taki sam.
Tak więc później robię
i = 3
nie zmienia tego, na co
j
wskazuje - i można to zamienić -j = 3
nie zmienia tego, na coi
wskazuje.Twój przykład nie usuwa odniesienia do listy
Więc kiedy to zrobisz:
i = [1,2,3] j = i
To to samo, co robienie tego:
i = j = [1,2,3]
więc
i
ij
oba wskazują na tę samą listę. Wtedy twój przykład zmienia listę:i[0] = 5
Listy Pythona są obiektami zmiennymi, więc kiedy zmienisz listę z jednego odniesienia i spojrzysz na nią z innego odniesienia, zobaczysz ten sam wynik, ponieważ jest to ta sama lista.
źródło
TLDR: nazwy Pythona działają jak wskaźniki z automatycznym usuwaniem / odwołaniem, ale nie pozwalają na jawne operacje wskaźnikowe. Inne cele reprezentują pośrednie, które zachowują się podobnie do wskaźników.
Implementacja CPython używa wskaźników typu
PyObject*
pod maską. W związku z tym możliwe jest przetłumaczenie semantyki nazw na operacje wskaźnikowe. Kluczem jest oddzielenie nazw od rzeczywistych obiektów .Przykładowy kod w Pythonie zawiera zarówno nazwy (
i
), jak i obiekty (5
).i = 5 # name `i` refers to object `5` j = i # ??? j = 3 # name `j` refers to object `3`
Można to z grubsza przetłumaczyć na kod C z oddzielnymi nazwami i obiektami.
int three=3, five=5; // objects int *i, *j; // names i = &five; // name `i` refers to position of object `5` j = i; // name `j` refers to referent of `i` j = &three; // name `j` refers to position of object `3`
Ważną częścią jest to, że „nazwy-wskaźniki” nie przechowują obiektów! Nie zdefiniowaliśmy
*i = five
, alei = &five
. Nazwy i przedmioty istnieją niezależnie od siebie.Nazwy wskazują tylko na istniejące obiekty w pamięci.
Podczas przypisywania nazwy do nazwy żadne obiekty nie są wymieniane! Kiedy definiujemy
j = i
, jest to równoważne zj = &five
. Ani jedno,i
anij
drugie nie jest połączone.+- name i -+ -\ \ --> + <five> -+ / | 5 | +- name j -+ -/ +----------+
W rezultacie zmiana celu jednej nazwy nie wpływa na drugą . Aktualizuje tylko to, na co wskazuje ta konkretna nazwa.
Python ma również inne rodzaje elementów przypominających nazwy : odwołania do atrybutów (
i.j
), subscriptions (i[j]
) i slicing (i[:j]
). W przeciwieństwie do nazw, które odnoszą się bezpośrednio do przedmiotów, wszystkie trzy odnoszą się pośrednio do elementów przedmiotów.Przykładowy kod zawiera obie nazwy (
i
) i subskrypcję (i[0]
).i = [1,2,3] # name `i` refers to object `[1, 2, 3]` j = i # name `j` refers to referent of `i` i[0] = 5 # ???
CPython
list
używa tablicyPyObject*
wskaźników w C pod maską. Można to z grubsza przetłumaczyć na kod C z oddzielnymi nazwami i obiektami.typedef struct{ int *elements[3]; } list; // length 3 `list` type int one = 1, two = 2, three = 3, five = 5; list values = {&one, &two, &three}; // objects list *i, *j; // names i = &values; // name `i` refers to object `[1, 2, 3]` j = i; // name `j` refers to referent of `i` i->elements[0] = &five; // leading element of `i` refers to object `5`
Ważne jest to, że nie zmieniliśmy żadnych nazw! Zmieniliśmy
i->elements[0]
element obiektu, na który wskazują nasze nazwy.Można zmieniać wartości istniejących obiektów złożonych.
Podczas zmiany wartości obiektu poprzez nazwę nazwy nie ulegają zmianie. Oba
i
ij
nadal odnoszą się do tego samego obiektu, którego wartość możemy zmienić.+- name i -+ -\ \ --> + <values> -+ / | elements | --> [1, 2, 3] +- name j -+ -/ +-----------+
Obiekt pośredni zachowuje się podobnie do wskaźnika, ponieważ możemy bezpośrednio zmienić to, na co wskazuje i odwoływać się do niego z wielu nazw.
źródło
i
ij
. Zaczynasz odi = 5
,j = 3
a następnie odwracasz je w pozostałej części swojego wpisu. To powiedziawszy ponownie, jest to jedyna odpowiedź imo, która odpowiada na pytanie w PO i naprawdę wyjaśnia, co dzieje się pod maską.Nie są do końca wskazówkami, są odniesieniami do obiektów. Obiekty mogą być zmienne lub niezmienne. Niezmienny obiekt jest kopiowany podczas modyfikacji. Zmienny obiekt jest zmieniany w miejscu. Liczba całkowita to niezmienny obiekt, do którego odwołujesz się za pomocą zmiennych i i j. Lista jest zmiennym obiektem.
W twoim pierwszym przykładzie
i=5 # The label i now references 5 j=i # The label j now references what i references j=3 # The label j now references 3 print i # i still references 5
W drugim przykładzie:
i=[1,2,3] # i references a list object (a mutable object) j=i # j now references the same object as i (they reference the same mutable object) i[0]=5 # sets first element of references object to 5 print j # prints the list object that j references. It's the same one as i.
źródło
Kiedy ustawisz
j=3
etykietę, któraj
nie ma już zastosowania (punktów)i
, zacznie wskazywać na liczbę całkowitą3
. Nazwai
jest wciąż odwołując się do wartości pierwotnie ustalonym,5
.źródło
każda zmienna znajdująca się po lewej stronie znaku „=” ma przypisaną wartość po prawej stronie znaku „=”
i = 5
j = i
--- j ma 5j = 3
--- j ma 3 (nadpisuje wartość 5), ale nic się nie zmieniło w odniesieniu do iprint(i)
- więc to drukuje 5źródło
Przypisanie nie modyfikuje obiektów; wszystko, co robi, to zmiana miejsca, w którym wskazuje zmienna. Zmiana miejsca, w którym jedna zmienna wskazuje, nie zmieni się tam, gdzie wskazuje inna.
Prawdopodobnie myślisz o tym, że listy i słowniki są zmiennymi typami. Istnieją operatory modyfikujące rzeczywiste obiekty na miejscu, a jeśli użyjesz jednego z nich, zobaczysz zmianę we wszystkich zmiennych wskazujących na ten sam obiekt:
x = [] y = x x.append(1) # x and y both are now [1]
Ale przypisanie nadal tylko przesuwa wskaźnik wokół:
x = [2] # x now points to new list [2]; y still points to old list [1]
Liczby, w przeciwieństwie do słowników i list, są niezmienne. Jeśli to zrobisz
x = 3; x += 2
, nie przekształcasz liczby 3 w liczbę 5;x
zamiast tego ustawiasz zmienną na 5. Trójka jest nadal niezmieniona, a wszystkie wskazujące na nią zmienne nadal będą widzieć 3 jako swoją wartość.(W rzeczywistej implementacji liczby prawdopodobnie w ogóle nie są typami referencyjnymi; bardziej prawdopodobne jest, że zmienne faktycznie zawierają reprezentację wartości bezpośrednio, a nie wskazują na nią. Jednak ten szczegół implementacji nie zmienia semantyki w przypadku typów niezmiennych .)
źródło
W Pythonie wszystko jest obiektem, w tym same fragmenty pamięci, które są zwracane. Oznacza to, że kiedy tworzony jest nowy fragment pamięci (niezależnie od tego, co utworzyłeś: int, str, obiekt niestandardowy itp.), Masz nowy obiekt pamięci. W twoim przypadku jest to przypisanie do 3, które tworzy nowy obiekt (pamięci) i tym samym ma nowy adres.
Jeśli wykonasz poniższe, łatwo zrozumiesz, o co mi chodzi.
i = 5 j = i print("id of j: {}", id(j)) j = 3 print("id of j: {}", id(j))
IMO, jeśli chodzi o pamięć, to jest kluczowa różnica między C i Pythonem. W C / C ++ zwracany jest wskaźnik pamięci (oczywiście jeśli używasz składni wskaźnika) zamiast obiektu pamięci, co zapewnia większą elastyczność w zakresie zmiany adresu.
źródło