Co się stanie, gdy przypiszesz wartość jednej zmiennej do innej zmiennej w Pythonie?


80

To mój drugi dzień nauki Pythona (znam podstawy C ++ i trochę OOP.) I mam pewne zamieszanie dotyczące zmiennych w Pythonie.

Oto jak je obecnie rozumiem:

Zmienne Pythona są referencjami (lub wskaźnikami?) Do obiektów (które są albo zmienne, albo niezmienne). Kiedy mamy coś podobnego num = 5, niezmienny obiekt 5jest tworzony gdzieś w pamięci, a para odwołań nazwa-obiekt numjest tworzona w określonej przestrzeni nazw. Kiedy mamy a = num, nic nie jest kopiowane, ale teraz obie zmienne odnoszą się do tego samego obiektu ia są dodawane do tej samej przestrzeni nazw.

To jest, gdy moja książka, Automate the nudne stuff with Python , dezorientuje mnie. Ponieważ jest to książka dla początkujących, nie wspomina o obiektach, przestrzeniach nazw itp. I próbuje wyjaśnić następujący kod:

>>> spam = 42
>>> cheese = spam
>>> spam = 100
>>> spam
100
>>> cheese
42

Wyjaśnienie, które oferuje, jest dokładnie takie samo jak w książce C ++, z czego nie jestem zadowolony, ponieważ mamy do czynienia z odniesieniami / wskaźnikami do obiektów. Więc w tym przypadku myślę, że w trzecim wierszu, ponieważ liczby całkowite są niezmienne, spamprzypisywany jest zupełnie nowy wskaźnik / odniesienie do innej lokalizacji w pamięci, tj. Pamięć, na którą wskazywał początkowo, nie była modyfikowana. Stąd mamy cheeseodniesienie do początkowego obiektu, do którego odnosi się spam. Czy to właściwe wyjaśnienie?



5
Przechowałeś 42w spam, teraz przechowywałeś spamw serze, oznacza cheese = 42, że po tym zamieniłeś spamna 100, nie edytowałeś cheese, dlatego cheesenadal jest 42.

15
Jak wskazuje Jonas w swojej odpowiedzi, niezmienność jest tutaj czerwonym śledziem: dokładnie taki sam wynik uzyskasz, jeśli użyjesz zmiennego obiektu, takiego jak lista. Chodzi o to, aby ponownie przypisać zmienną do nowego odniesienia.
Daniel Roseman,


27
@mini: Wolę myśleć o zmiennych Pythona jako o etykietach, a nie o zasobnikach. Przyklejasz etykietę spamdo numeru 42. Następnie przyklejasz etykietę cheesedo rzeczy oznaczonej jako spam(nie na samej etykiecie, pamiętaj). Potem zdjąłeś z tego spametykietkę i umieściłeś ją na numerze 100.
Tim Pederick.

Odpowiedzi:


81

Jako programista C ++ możesz traktować zmienne Pythona jako wskaźniki.

Zatem kiedy piszesz spam = 100, oznacza to, że „przypisujesz wskaźnik”, który poprzednio wskazywał na obiekt 42, aby wskazywał na obiekt 100.

Wcześniej cheesezostał przypisany do wskazywania tego samego obiektu spam, który wskazywał, a który akurat był 42w tym czasie. Ponieważ nie dokonałeś modyfikacji cheese, nadal wskazuje na 42.

Niezmienność nie ma z tym nic wspólnego w tym przypadku, ponieważ przypisanie wskaźnika nie zmienia niczego w wskazywanym obiekcie.


2
to samo co obiekty w js
Dushyant Bangal

7
Niezmienność jest ważna, ponieważ oznacza, że ​​można bezpiecznie traktować odwołanie tak, jakby to była wartość. Traktowanie zmiennych obiektów tak, jakby były wartościami, jest bardziej ryzykowne.
plugwash

21

Z mojego punktu widzenia istnieją różne poglądy na język.

  • Perspektywa „prawnika językowego”.
  • Perspektywa „praktycznego programisty”.
  • perspektywa „realizatora”.

Z punktu widzenia prawnika językowego zmienne Pythona zawsze „wskazują” obiekt. Jednak w przeciwieństwie do Java i C ++ zachowanie == <=> = etc zależy od typu środowiska wykonawczego obiektów, na które wskazują zmienne. Ponadto w Pythonie zarządzanie pamięcią jest obsługiwane przez język.

Z praktycznego punktu widzenia programisty możemy traktować fakt, że liczby całkowite, łańcuchy, krotki itp. Są niezmiennymi * obiektami, a nie wartościami prostymi, jako nieistotny szczegół. Wyjątkiem jest, gdy przechowujemy duże ilości danych liczbowych, możemy chcieć użyć typów, które mogą przechowywać wartości bezpośrednio (np. Tablice numpy), zamiast typów, które kończą się tablicą pełną odwołań do małych obiektów.

Z punktu widzenia implementujących większość języków ma jakąś regułę, która zakłada, że ​​jeśli określone zachowania są poprawne, implementacja jest poprawna, niezależnie od tego, jak rzeczy są faktycznie wykonywane pod maską.

Więc tak, twoje wyjaśnienie jest poprawne z punktu widzenia prawnika językowego. Twoja książka jest poprawna z praktycznego punktu widzenia programisty. To, co faktycznie robi implementacja, zależy od implementacji. W cpythonie liczby całkowite są obiektami rzeczywistymi, chociaż liczby całkowite o małych wartościach są pobierane z puli pamięci podręcznej, a nie tworzone od nowa. Nie jestem pewien, co robią inne implementacje (np. Pypy i jython).

* zwróć uwagę na różnicę między obiektami zmiennymi i niezmiennymi. W przypadku obiektu zmiennego musimy uważać, aby traktować go „jak wartość”, ponieważ jakiś inny kod może go zmodyfikować. W przypadku niezmiennego przedmiotu nie mamy takich obaw.


20

Prawdą jest, że jako wskaźniki można w mniejszym lub większym stopniu uwzględniać zmienne. Jednak przykładowy kod bardzo pomógłby w wyjaśnieniu, jak to faktycznie działa.

Po pierwsze, będziemy intensywnie wykorzystywać tę idfunkcję:

Zwróć „tożsamość” obiektu. Jest to liczba całkowita, która na pewno będzie niepowtarzalna i stała dla tego obiektu podczas jego życia. Dwa obiekty z nienakładającymi się okresami istnienia mogą mieć tę samą wartość id ().

Prawdopodobnie zwróci to różne wartości bezwzględne na twoim komputerze.

Rozważmy ten przykład:

>>> foo = 'a string'
>>> id(foo) 
4565302640
>>> bar = 'a different string'
>>> id(bar)
4565321816
>>> bar = foo
>>> id(bar) == id(foo)
True
>>> id(bar)
4565302640

Możesz to zobaczyć:

  • Oryginalne foo / bar mają różne identyfikatory, ponieważ wskazują różne obiekty
  • Gdy bar jest przypisany do foo, ich identyfikatory są teraz takie same. Jest to podobne do obu wskazujących na to samo miejsce w pamięci, które widzisz podczas tworzenia wskaźnika C ++

kiedy zmieniamy wartość foo, przypisujemy mu inny id:

>>> foo = 42
>>> id(foo)
4561661488
>>> foo = 'oh no'
>>> id(foo)
4565257832

Ciekawą obserwacją jest również to, że liczby całkowite domyślnie mają tę funkcjonalność do 256:

>>> a = 100
>>> b = 100
>>> c = 100
>>> id(a) == id(b) == id(c)
True

Jednak powyżej 256 nie jest to już prawdą:

>>> a = 256
>>> b = 256
>>> id(a) == id(b)
True
>>> a = 257
>>> b = 257
>>> id(a) == id(b)
False

jednak przypisanie ado brzeczywiście zachowa identyfikator taki sam, jak pokazano wcześniej:

>>> a = b
>>> id(a) == id(b)
True

18

Python nie jest przekazywaniem przez odniesienie ani przekazywaniem wartości. Zmienne Pythona nie są wskaźnikami, nie są odniesieniami, nie są wartościami. Zmienne Pythona to nazwy .

Potraktuj to jako „pass-by-alias”, jeśli potrzebujesz tego samego typu frazy lub ewentualnie „pass-by-object”, ponieważ możesz zmutować ten sam obiekt z dowolnej zmiennej, która go wskazuje, jeśli jest zmienna, ale ponowne przypisanie zmienna (alias) zmienia tylko tę jedną zmienną.

Jeśli to pomoże: Zmienne C to pola, w których wpisujesz wartości. Nazwy Pythona to tagi, które umieszczasz na wartościach.

Nazwa zmiennej Pythona jest kluczem w globalnej (lub lokalnej) przestrzeni nazw, która w rzeczywistości jest słownikiem. Podstawową wartością jest jakiś obiekt w pamięci. Przypisanie nadaje nazwę temu obiektowi. Przypisanie jednej zmiennej do innej zmiennej oznacza, że ​​obie zmienne są nazwami tego samego obiektu. Ponowne przypisanie jednej zmiennej zmienia nazwę obiektu przez tę zmienną bez zmiany innej zmiennej. Przesunąłeś tag, ale nie zmieniłeś poprzedniego obiektu ani żadnych innych znaczników na nim.

W kodzie C będącym podstawą implementacji CPythona każdy obiekt Pythona jest a PyObject*, więc możesz myśleć o nim jak o C, jeśli kiedykolwiek miałeś wskaźniki do danych (bez wskaźników do wskaźników, żadnych bezpośrednio przekazywanych wartości).

można powiedzieć, że Python jest przekazywany przez wartość, gdzie wartości są wskaźnikami… lub można powiedzieć, że Python jest przekazywany przez odniesienie, gdzie odniesienia są kopiami.


1
Problem z nazywaniem go „pass-by-name” polega na tym, że istnieje już konwencja przekazywania parametrów zwana „call by name”, o zupełnie innym znaczeniu. W wywołaniu według nazwy wyrażenie parametru jest oceniane za każdym razem, gdy funkcja używa parametru i nigdy nie jest oceniane, jeśli funkcja nie używa parametru.
user2357112 obsługuje Monikę

11

Po uruchomieniu spam = 100Pythona utwórz w pamięci jeszcze jeden obiekt, ale nie zmieniaj istniejącego. więc nadal masz wskaźnik cheesedo 42 i spamdo 100


8

To, co dzieje się w spam = 100linii, to zamiana poprzedniej wartości (wskaźnik na obiekt typu intz wartością 42) na inny wskaźnik na inny obiekt (typ int, wartość 100)


Liczby całkowite to obiekt wartości znajdujący się na stosie, prawda?
Gert Kommer

Tak, są czymś w rodzaju obiektu, który tworzysz za pomocą new Class()składni w C ++. Co więcej, w Pythonie wszystko jest instancją objectklasy / podklasy.
bakatrouble,

4
@GertKommer w CPythonie przynajmniej wszystkie obiekty żyją na stercie. Nie ma rozróżnienia na „przedmiot wartości”. Są tylko przedmioty i wszystko jest przedmiotem . Dlatego rozmiar typowego int wynosi około 28 bajtów, w zależności od wersji Pythona, ponieważ ma on cały narzut Py_Object. Małe liczby typu int są buforowane jako optymalizacja CPythona.
juanpa.arrivillaga

8

Jak @DeepSpace wspomniał w komentarzach, Ned Batchelder wykonuje świetną robotę, wyjaśniając zmienne (nazwy) i przypisania do wartości na blogu, z którego wygłosił wykład na PyCon 2015, Facts and Myths about Python names and values . Może to być wnikliwe dla Pythonistas na każdym poziomie zaawansowania.


1

W Pythonie zmienna przechowuje odniesienie do obiektu . Obiekt to kawał przydzielonej pamięci, która przechowuje wartość i nagłówek . Nagłówek obiektu zawiera jego typ i licznik odwołań, który wskazuje, ile razy ten obiekt jest przywoływany w kodzie źródłowym, aby funkcja Garbage Collection mogła zidentyfikować, czy obiekt może zostać zebrany.

Teraz, kiedy przypisujesz wartości do zmiennej, Python w rzeczywistości przypisuje odniesienia, które są wskaźnikami do lokalizacji pamięci przydzielonych do obiektów:

# x holds a reference to the memory location allocated for  
# the object(type=string, value="Hello World", refCounter=1)

x = "Hello World" 

Teraz, gdy przypisujesz obiekty różnego typu do tej samej zmiennej, w rzeczywistości zmieniasz odniesienie tak, aby wskazywało na inny obiekt (tj. Inną lokalizację w pamięci). Do czasu, gdy przypiszesz inne odwołanie (a tym samym obiekt) do zmiennej, Garbage Collector natychmiast odzyska miejsce przydzielone do poprzedniego obiektu, zakładając, że nie odwołuje się do niego żadna inna zmienna w kodzie źródłowym:

# x holds a reference to the memory location allocated for  
# the object(type=string, value="Hello World", refCounter=1)

x = "Hello World" 

# Now x holds the reference to a different object(type=int, value=10, refCounter=1)
# and object(type=string, value="Hello World", refCounter=0) -which is not refereced elsewhere
# will now be garbage-collected.
x = 10

Przechodząc teraz do twojego przykładu,

spam przechowuje odniesienie do obiektu (typ = int, wartość = 42, refCounter = 1):

>>> spam = 42

Teraz cheesebędzie również przechowywać odniesienie do obiektu (typ = int, value = 42, refCounter = 2)

>>> cheese = spam

Teraz spam zawiera odniesienie do innego obiektu (typ = int, wartość = 100, refCounter = 1)

>>> spam = 100
>>> spam
100

Ale ser będzie nadal wskazywał na obiekt (typ = int, wartość = 42, refCounter = 1)

>>> cheese
42

0

Kiedy przechowujesz spam = 42, tworzy obiekt w pamięci. Następnie należy przypisać cheese = spamto przypisuje obiekt odwołuje się spamdo cheese. I wreszcie, kiedy się zmieniasz spam = 100, zmienia się tylko spamobiekt. A więc cheese = 42.


7
„Następnie przypisujesz ser = spam, tworzy on kolejny obiekt w pamięci” Nie, nie robi. Przypisuje obiekt, do którego odwołuje się spamdo cheese. Nie są tworzone żadne nowe obiekty.
juanpa.arrivillaga

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.