Чи є вказівниками змінні python? чи ще які вони?


82

Наскільки я знаю, змінні в Python - це лише вказівники.

Виходячи з цього правила, я можу припустити, що результат для цього фрагмента коду:

i = 5
j = i
j = 3 
print(i)

буде 3. Але я отримав несподіваний для мене результат, він був 5.

Більше того, моя книга про Python висвітлює цей приклад:

i = [1,2,3]
j = i
i[0] = 5
print(j)

результат буде [5,2,3].

Що я неправильно розумію?



5
Я не розумію, як iслід було дорівнювати3
Tooniis 22.03.18

@Tooniis j тепер вказує на інший блок пам'яті, відмінний від того, на який я вказую. Ви напевно вже зрозуміли, але про всяк випадок, коли це побачить хтось інший
Swaroop Joshi

Відповіді:


89

Ми називаємо їх посиланнями. Вони працюють так

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)

Невеликі танки інтернуються, але це не важливо для цього пояснення

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
Привіт, Джон, що ти маєш на увазі під словом "маленькі інтерни інтерновані"? Дякую!
yuqli

6
@yuqli У python все є об'єктом, включаючи числа. Оскільки малі числа (-5,256) використовуються дуже часто, їх "інтернують" або кешують у CPython. Таким чином, кожен раз, коли ви вводите 40, ви посилаєтесь на той самий об’єкт у пам’яті. Щоб побачити цей тип a, b = 256 і протестуйте a is b. Тепер спробуйте з a, b = 257. Див: stackoverflow.com/a/1136852/3973834 і codementor.io/python/tutorial / ...
Еван Rosica

3
З мого досвіду, називання їх "іменами" є більш поширеним серед розробників Python. Термін "посилання" поставляється з непотрібним багажем C, і, можливо, занадто сильно зміщує Python ( мову ) до CPython ( реалізація ), який, як правило, використовує підрахунок посилань.
wim

33

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

У вашому першому прикладі ім'я iприв'язане до значення 5. Прив’язка різних значень до імені jне впливає i, тому при друку пізніше значення iзначення залишається нерухомим 5.

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

Зауважте, що було б неправильно, якщо б ви сказали "обидва списки змінилися". Існує лише один список, але він має дві назви ( iі j), які на нього посилаються.

Супутня документація


15

Змінні Python - це імена, прив’язані до об’єктів

З документів :

Назви відносяться до об’єктів. Імена вводяться за допомогою операцій прив’язки імен. Кожне входження імені в текст програми відноситься до прив'язки цього імені, встановленого у внутрішньому функціональному блоці, що містить використання.

Коли ви це зробите

i = 5
j = i

це те саме, що робити:

i = 5
j = 5

jне вказує iі після призначення jне знає, що iіснує. jпросто пов'язаний з тим, на що iвказував під час призначення.

Якби ви виконували завдання в одному рядку, це виглядало б так:

i = j = 5

І результат був би точно таким же.

Таким чином, пізніше робити

i = 3

не змінює того, на що jвказує - і ви можете поміняти його місцями - j = 3не змінить того, на що iвказує.

Ваш приклад не видаляє посилання на список

Отже, коли ви робите це:

i = [1,2,3]
j = i

Це те саме, що робити це:

i = j = [1,2,3]

так iі jобидва вказують на один і той же список. Тоді ваш приклад мутує список:

i[0] = 5

Списки Python є змінними об'єктами, тому, коли ви змінюєте список з одного посилання, а ви переглядаєте його з іншого посилання, ви побачите той самий результат, оскільки це той самий список.


9

TLDR: Імена Python працюють як вказівники з автоматичним відключенням / посиланням, але не дозволяють явних операцій вказівника. Інші цілі представляють опосередкованість, яка поводиться подібно до покажчиків.


Реалізація CPython використовує покажчики типуPyObject* під капотом. Таким чином, можна перевести семантику імен на операції покажчика. Головне - відокремити імена від реальних об’єктів .

Приклад коду Python включає як імена ( i), так і об'єкти ( 5).

i = 5  # name `i` refers to object `5`
j = i  # ???
j = 3  # name `j` refers to object `3`

Це можна приблизно перекласти на код C з окремими іменами та об'єктами.

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`

Важливим є те, що "імена як покажчики" не зберігають об'єкти! Ми не визначали *i = five, але i = &five. Назви та предмети існують незалежно один від одного.

Імена вказують лише на наявні об'єкти в пам'яті.

При присвоєнні від імені до імені ніякі об'єкти не обмінюються! Коли ми визначаємо j = i, це еквівалентно j = &five. Ні ті, iні інші jне пов'язані.

+- name i -+ -\
               \
                --> + <five> -+
               /    |        5 |
+- name j -+ -/     +----------+

Як результат, зміна цілі одного імені не впливає на інше . Він лише оновлює те, на що вказує ця конкретна назва.


Python також має інші види елементів, подібних до імен : посилання на атрибути ( i.j), підписки ( i[j]) та нарізання ( i[:j]). На відміну від імен, що стосуються безпосередньо об’єктів, усі три опосередковано стосуються елементів об’єктів.

Приклад коду включає як імена ( i), так і підписку ( 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використовує масив PyObject*покажчиків C під капотом. Це знову можна приблизно перекласти на код C з окремими іменами та об'єктами.

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`

Важливим є те, що ми не змінювали жодної назви! Ми все-таки змінились i->elements[0], елемент об’єкта, на який вказують наші імена.

Значення існуючих складених об'єктів можуть бути змінені.

При зміні значення об’єкта через ім’я імена не змінюються. Обидва iі jвсе ще стосуються одного і того ж об’єкта, значення якого ми можемо змінити.

+- name i -+ -\
               \
                --> + <values> -+
               /    |  elements | --> [1, 2, 3]
+- name j -+ -/     +-----------+

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


1
Мені дуже подобається ця відповідь, але, на мою думку, ви перевернули iі jзавдання у своєму прикладі. Ви починаєте з i = 5, j = 3а потім інвертуєте їх у іншій частині вашого допису. Повторно сказано, це єдина відповідь imo, яка відповідає питанню в ОП і справді пояснює, що відбувається під капотом.
Джеремі Редкліфф

1
@jeremyradcliff Дякуємо за увагу. Це слід виправити зараз. Повідомте мене, якщо я ще щось пропустив.
MisterMiyagi

7

Вони не зовсім вказівники, це посилання на об’єкти. Об'єкти можуть бути як змінними, так і незмінними. При зміні незмінний об'єкт копіюється. Змінний об'єкт змінюється на місці. Ціле число - це незмінний об’єкт, на який ви посилаєтесь за допомогою змінних i та j. Список є змінним об’єктом.

У вашому першому прикладі

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

У другому прикладі:

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.

"Незмінний об'єкт копіюється, коли його змінюють." Це трохи суперечливо.
PM 2Кольцо

1

Коли встановлено, j=3що мітка jбільше не застосовується (вказує) i, вона починає вказувати на ціле число 3. Назва iпо - , як і раніше зі посиланням на значення, встановлені спочатку 5.


1

яка коли-небудь змінна знаходиться ліворуч від знака '=', присвоюється значення праворуч від '='

i = 5

j = i --- j має 5

j = 3 --- j має 3 (замінює значення 5), але щодо i нічого не змінилося

print(i)- отже, це друкує 5


1

Призначення не змінює об'єкти; все, що вона робить, це змінювати, де вказує змінна. Зміна, де одна точка змінної не зміниться, де вказує інша точка.

Ви, мабуть, замислюєтесь над тим, що списки та словники є змінними типами. Є оператори, які змінюють фактичні об’єкти на місці, і якщо ви використовуєте один із них, ви побачите зміну у всіх змінних, що вказують на той самий об’єкт:

x = []
y = x
x.append(1)
# x and y both are now [1]

Але призначення все одно просто рухає вказівник навколо:

x = [2]
# x now points to new list [2]; y still points to old list [1]

Числа, на відміну від словників та списків, незмінні. Якщо ви це зробите x = 3; x += 2, ви не перетворюєте число 3 в число 5; ви просто робите змінну xвказувати на 5 замість цього. 3 все ще залишається незмінним, і будь-які вказуючі на нього змінні все ще бачитимуть 3 як своє значення.

(У фактичній реалізації числа, ймовірно, взагалі не є посилальними типами; швидше за все, змінні насправді містять подання значення безпосередньо, а не вказують на нього. Але ця деталь реалізації не змінює семантики, коли йдеться про незмінні типи. .)


1
Це не означає тип значення. Тип значення означає саме те, що ви описали в останньому абзаці (що значення передається / копіюється навколо замість посилання на об'єкт), і це не так внутрішньо (у CPython та в PyPy без компілятора JIT - кожне ціле число є виділений купою об'єкт). Просто дотримуйтесь незмінного, саме це слово вам там потрібно.

-1

У Python все є об'єктом, включаючи самі фрагменти пам'яті, які ви повертаєте. Це означає, що коли створюється новий фрагмент пам'яті (незалежно від того, що ви створили: int, str, користувацький об'єкт тощо), у вас є новий об'єкт пам'яті. У вашому випадку це присвоєння 3, яке створює новий (пам'ять) об'єкт і, отже, має нову адресу.

Якщо ви запустите наступне, ви легко зрозумієте, що я маю на увазі.

i = 5
j = i
print("id of j: {}", id(j))
j = 3
print("id of j: {}", id(j))

IMO, пам’ятаючи про пам’ять, це ключове розуміння / різниця між C та Python. У C / C ++ вам повертають вказівник пам'яті (якщо ви, звичайно, використовуєте синтаксис вказівника), замість об'єкта пам'яті, що надає вам більшу гнучкість щодо зміни вказаної адреси.

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