Що відбувається, коли ви присвоюєте значення однієї змінної іншій змінній у Python?


80

Це мій другий день вивчення python (я знаю основи C ++ та деякі ООП.), І у мене є невелика плутанина щодо змінних у python.

Ось як я їх розумію зараз:

Змінні Python - це посилання (або вказівники?) На об'єкти (які можуть бути змінними або незмінними). Коли ми маємо щось на зразок num = 5, незмінний об'єкт 5створюється десь у пам'яті, а пара посилань ім'я-об'єкт numстворюється в певному просторі імен. Коли ми маємо a = num, нічого не копіюється, але тепер обидві змінні посилаються на один і той же об’єкт і aдодаються до одного простору імен.

Тут мене бентежить моя книга « Автоматизувати нудні речі за допомогою Python» . Оскільки це новачок, він не згадує об’єкти, простори імен тощо, і намагається пояснити наступний код:

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

Пояснення, яке він пропонує, точно таке ж, як і в книзі C ++, що я не задоволений, оскільки ми маємо справу з посиланнями / вказівниками на об'єкти. Отже, у цьому випадку, я припускаю, що в 3-му рядку, оскільки цілі числа незмінні, spamпризначається абсолютно новий покажчик / посилання на інше місце в пам'яті, тобто пам’ять, на яку вона вказувала спочатку, не була змінена. Отже, ми маємо на cheeseувазі початковий об'єкт, на який посилається spam. Це правильне пояснення?



5
Ви зберігали 42в spam, Тепер ви зберігаєте spamв сирі означає cheese = 42, Після цього ви замінили spamна 100, Ви не редагували cheese, тому cheeseдосі 42.

15
Як зазначає Йонас у своїй відповіді, незмінність тут є червоною оселедцем: ви отримали б точно такий же результат, якби використовували мінливий об'єкт, як список. Справа в тому, що ви перепризначаєте змінну новому посиланню.
Даніель Роузман,


27
@mini: Я вважаю, що змінні Python вважаються мітками, а не сегментами. Ви наклеїли ярлик spamна номер 42. Далі ви наклеїли ярлик cheeseна річ, позначену як spam(не зверху на саму етикетку, майте на увазі). Потім ви відклеїли spamярлик від цього і поставили його під номером 100.
Тім Педерік

Відповіді:


81

Як розробник C ++ ви можете вважати змінні Python вказівниками.

Таким чином, коли ви пишете spam = 100, це означає, що ви "призначаєте вказівник", який раніше вказував на об'єкт 42, вказувати на об'єкт 100.

Раніше, cheeseбуло призначено вказувати на той самий об'єкт, на який spamвказували, що трапилося42 на той час. Оскільки ви не модифікували cheese, це все одно вказує на 42.

У цьому випадку незмінність не має нічого спільного, оскільки призначення покажчика нічого не змінює щодо об’єкта, на який вказують.


2
те саме, що і об'єкти в js
Душ'янт Бангал

7
Незмінність важлива, оскільки це означає, що ви можете безпечно поводитися з посиланням так, ніби це значення. Поводження зі змінними об’єктами як із цінностями є більш ризикованим.
plugwash

21

На мою думку, це різні погляди на мову.

  • Перспектива "мовного юриста".
  • Перспектива "практичного програміста".
  • перспектива "реалізатора".

З точки зору мовного юриста змінні python завжди "вказують" на об'єкт. Однак, на відміну від Java та C ++, behvaiour == <=> = etc залежить від типу виконання об'єктів, на які вказують змінні. Крім того, у python управління пам'яттю обробляється мовою.

З точки зору практичного програміста ми можемо розглядати той факт, що цілі числа, рядки, кортежі тощо є незмінними * об'єктами, а не прямими значеннями, як недоречні деталі. Виняток полягає в тому, що при зберіганні великих обсягів числових даних ми можемо захотіти використовувати типи, які можуть зберігати значення безпосередньо (наприклад, масиви numpy), а не типи, які в кінцевому підсумку матимуть масив, повний посилань на крихітні об'єкти.

З точки зору реалізаторів, більшість мов мають якесь правило начебто таке, що якщо вказана поведінка правильна, реалізація правильна, незалежно від того, як насправді все робиться під капотом.

Так що так, ваше пояснення є правильним з точки зору мовного юриста. Ваша книга правильна з точки зору практичного програміста. Те, що реалізація насправді робить, залежить від реалізації. У cpython цілі числа - це реальні об'єкти, хоча цілі числа малого значення беруться з пулу кешу, а не створюються заново. Я не впевнений, що роблять інші реалізації (наприклад, pypy та jython).

* зверніть увагу на різницю між змінними та незмінними об'єктами тут. З мінливим об'єктом ми повинні бути обережними, ставлячись до нього "як до значення", оскільки якийсь інший код може його мутувати. З незмінним об’єктом у нас таких проблем немає.


20

Правильно, ви можете більш-менш використовувати змінні як вказівники. Однак приклад коду може дуже допомогти у поясненні того, як це насправді працює.

По-перше, ми будемо активно використовувати idфункцію:

Поверніть "ідентичність" об'єкта. Це ціле число, яке гарантовано буде унікальним і постійним для цього об’єкта протягом його життя. Два об’єкти, що не перекривають тривалість життя, можуть мати однакове значення id ().

Цілком ймовірно, це поверне різні абсолютні значення на вашій машині.

Розглянемо цей приклад:

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

Ви бачите, що:

  • Оригінал foo / bar має різні ідентифікатори, оскільки вони вказують на різні об'єкти
  • Коли foo присвоєно foo, їх ідентифікатори тепер однакові. Це схоже на те, що вони обидва вказують на те саме місце в пам'яті, що ви бачите при створенні вказівника на C ++

коли ми змінюємо значення foo, воно присвоюється іншому ідентифікатору:

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

Цікавим спостереженням є також те, що цілі числа неявно мають цю функціональність до 256:

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

Однак після 256 це вже неправда:

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

Однак призначення aна bбуде дійсно тримати ідентифікатор так само , як було показано вище:

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

18

Python не є ні передавальним посиланням, ні переданим значенням. Змінні Python не є покажчиками, вони не є посиланнями, вони не є значеннями. Змінні Python - це імена .

Подумайте про це як про "передачу-псевдонім", якщо вам потрібен той самий тип фрази, або, можливо, про "передачу-об'єкт", оскільки ви можете мутувати той самий об'єкт із будь-якої змінної, яка вказує на нього, якщо він змінюється, але перепризначення змінна (псевдонім) змінює лише одну змінну.

Якщо це допомагає: Змінні C - це поля, в які ви пишете значення. Імена Python - це теги, які ви ставите на значення.

Ім'я змінної Python є ключем у глобальному (або локальному) просторі імен, що фактично є словником. Основне значення - це якийсь об’єкт у пам’яті. Присвоєння дає назву цьому об’єкту. Присвоєння однієї змінної іншій змінній означає, що обидві змінні є іменами одного і того ж об'єкта. Повторне присвоєння однієї змінної змінює об'єкт, який називається цією змінною, не змінюючи іншу змінну. Ви перемістили тег, але не змінили попередній об'єкт або будь-які інші теги на ньому.

У базовому коді C реалізації CPython кожен об'єкт Python є a PyObject*, тому ви можете вважати його таким, що працює як C, якщо у вас коли-небудь були вказівники на дані (відсутні вказівники на вказівники, відсутні безпосередньо передані значення).

ви можете сказати, що Python є передавальним значенням, де значення є покажчиками ... або ви можете сказати, що Python є передавальним посиланням, де посилання є копіями.


1
Проблема з його викликом "передавати ім'я" полягає в тому, що вже існує умова передачі параметрів під назвою "виклик за іменем", із зовсім іншим значенням. У виклику за іменем вираз параметра обчислюється кожного разу, коли функція використовує параметр, і ніколи не обчислюється, якщо функція не використовує параметр.
user2357112 підтримує Моніку

11

При запуску spam = 100python створіть ще один об'єкт в пам'яті, але не змінюйте існуючий. тож у вас все ще є покажчик cheeseна 42 і spamна 100


8

Те, що відбувається в spam = 100рядку, це заміна попереднього значення (вказівник на об'єкт типу intна значення 42) іншим вказівником на інший об'єкт (тип int, значення 100)


Цілі числа - це об’єкт значення, який знаходиться у стеку, чи не так?
Gert Kommer

Так, це щось на зразок об’єкта, який ви створюєте за допомогою new Class()синтаксису в C ++. Більше того, у Python будь-що є екземпляром objectкласу / підкласу.
bakatrouble

4
@GertKommer принаймні в CPython, всі об'єкти живуть у купі. Не існує розмежування "об’єкта цінності". Є просто предмети, і все є об’єктом . Ось чому розмір типового int становить близько 28 байт, залежно від версії Python, оскільки він має всі накладні витрати на Py_Object. Малі вставки кешуються як оптимізація CPython.
juanpa.arrivillaga

8

Як згадувалося @DeepSpace у коментарях, Нед Батчелдер чудово справляється з демістифікацією змінних (імен) та призначенням значень у блозі, з якого він виступив з доповіддю на PyCon 2015 « Факти та міфи про імена та значення Python» . Це може бути проникливим для пітоністів на будь-якому рівні володіння.


1

У Python змінна містить посилання на об'єкт . Об'єкт є шматком виділеної пам'яті , яка зберігає значення і заголовок . Заголовок об'єкта містить його тип і лічильник посилань, який позначає кількість посилань на цей об'єкт у вихідному коді, щоб Garbage Collection міг визначити, чи можна збирати об'єкт.

Тепер, коли ви присвоюєте значення змінної, Python фактично призначає посилання, які є вказівниками на місця пам'яті, виділені об'єктам:

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

x = "Hello World" 

Тепер, коли ви присвоюєте об'єкти різного типу одній і тій же змінній, ви фактично змінюєте посилання таким чином, щоб воно вказувало на інший об'єкт (тобто інше розташування пам'яті). До того часу, коли ви призначите іншу посилання (і, отже, об'єкт) для змінної, Сміттєвий збирач негайно поверне простір, виділений попередньому об'єкту, припускаючи, що на нього не посилається жодна інша змінна у вихідному коді:

# 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

Переходячи до вашого прикладу зараз,

spam містить посилання на об'єкт (тип = int, значення = 42, refCounter = 1):

>>> spam = 42

Тепер cheeseтакож буде міститися посилання на об'єкт (type = int, value = 42, refCounter = 2)

>>> cheese = spam

Тепер спам містить посилання на інший об'єкт (тип = int, значення = 100, refCounter = 1)

>>> spam = 100
>>> spam
100

Але сир буде продовжувати вказувати на предмет (type = int, value = 42, refCounter = 1)

>>> cheese
42

0

Коли ви зберігаєте spam = 42, він створює об’єкт у пам’яті. Потім ви призначаєте cheese = spam, він привласнює об'єкт , на який посилається spamна cheese. І нарешті, коли ви змінюєтесь spam = 100, він змінює лише spamоб’єкт. Отже cheese = 42.


7
"Тоді ви призначаєте сир = спам, він створює інший об'єкт у пам'яті" Ні, це не так. Він привласнює об'єкт , на який посилається spamна cheese. Нові об'єкти не створюються.
juanpa.arrivillaga

-1

Сторінка функції numpy.copy () має пояснення

https://docs.scipy.org/doc/numpy/reference/generated/numpy.copy.html

Приклад, який він подає, такий:

Створіть масив x із посиланням y та копією z:

x = np.array([1, 2, 3])
y = x
z = np.copy(x)

Зверніть увагу, що коли ми модифікуємо x, y змінюється, але не z:

x[0] = 10
x[0] == y[0]
True
x[0] == z[0]
False
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.