Czy zmienne Pythona są wskaźnikami? albo czym one są?


82

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?



5
Nie rozumiem, jak ipowinno być równe3
Tooniis

@Tooniis j wskazuje teraz na inny blok pamięci, inny niż ten, na który wskazuję. Prawdopodobnie już zrozumiałeś, ale na wypadek, gdyby ktoś to zobaczył
Swaroop Joshi

Odpowiedzi:


89

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

3
Cześć John, co masz na myśli, mówiąc „Mali intersi są internowani”? Dzięki!
yuqli

6
@yuqli W Pythonie wszystko jest obiektem, łącznie z liczbami. Ponieważ małe liczby (-5,256) są używane bardzo często, są one „internowane” lub buforowane w CPythonie. Tak więc za każdym razem, gdy wpisujesz 40, odwołujesz się do tego samego obiektu w pamięci. Aby zobaczyć ten typ a, b = 256 i przetestuj a is b. Teraz spróbuj z a, b = 257. Zobacz: stackoverflow.com/a/1136852/3973834 i codementor.io/python/tutorial/…
Evan Rosica

3
Z mojego doświadczenia wynika , że nazywanie ich „imionami” jest bardziej powszechne wśród programistów Pythona. Termin „referencje” zawiera niepotrzebny bagaż w języku C i być może zbytnio promuje Pythona ( język ) w kierunku CPythona ( implementacja ), który używa liczenia odwołań.
wim

33

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 ijest powiązana z wartością 5. Powiązanie różnych wartości z nazwą jnie ma żadnego wpływu na to i, więc podczas późniejszego drukowania wartość itej wartości jest nadal 5.

W swoim drugim przykładzie powiązać oba ii jdo 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 ( ii j), które się do niej odnoszą.

Powiązana dokumentacja


15

Zmienne Pythona to nazwy powiązane z obiektami

Z dokumentów :

Nazwy odnoszą się do przedmiotów. Nazwy są wprowadzane przez operacje wiązania nazw. Każde wystąpienie nazwy w tekście programu odnosi się do powiązania tej nazwy ustanowionego w najbardziej wewnętrznym bloku funkcyjnym zawierającym użycie.

Kiedy to zrobisz

i = 5
j = i

to jest to samo co robienie:

i = 5
j = 5

jnie wskazuje i, a po przypisaniu jnie wie, że iistnieje. jjest po prostu związany z tym, na co iwskazywał 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 jwskazuje - i można to zamienić - j = 3nie zmienia tego, na co iwskazuje.

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 ii joba 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.


9

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 typuPyObject* 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, ale i = &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 z j = &five. Ani jedno, iani jdrugie 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 listużywa tablicy PyObject*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 ii jnadal 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.


1
Naprawdę podoba mi się ta odpowiedź, ale myślę, że w swoim przykładzie odwróciliście przypisania ii j. Zaczynasz od i = 5, j = 3a 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ą.
jeremy radcliff

1
@jeremyradcliff Dzięki za ostrzeżenie. Powinien zostać naprawiony teraz. Daj mi znać, jeśli trochę mi brakowało.
MisterMiyagi

7

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.

„Niezmienny obiekt jest kopiowany podczas modyfikacji”. To trochę sprzeczne.
2:00 po południu,

1

Kiedy ustawisz j=3etykietę, która jnie ma już zastosowania (punktów) i, zacznie wskazywać na liczbę całkowitą 3. Nazwa ijest wciąż odwołując się do wartości pierwotnie ustalonym, 5.


1

każda zmienna znajdująca się po lewej stronie znaku „=” ma przypisaną wartość po prawej stronie znaku „=”

i = 5

j = i --- j ma 5

j = 3 --- j ma 3 (nadpisuje wartość 5), ale nic się nie zmieniło w odniesieniu do i

print(i)- więc to drukuje 5


1

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; xzamiast 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 .)


1
Nie o to chodzi w typie wartości. Typ wartości oznacza dokładnie to, co opisałeś w ostatnim akapicie (że wartość jest przekazywana / kopiowana zamiast odniesienia do obiektu), a tak nie jest wewnętrznie (w CPythonie i kompilatorze PyPy sans JIT - każda liczba całkowita jest obiekt przydzielony na stertę). Po prostu trzymaj się niezmienności, właśnie tego potrzebujesz.

-1

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.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.