Питання Як мені передати змінну за посиланням?


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

class PassByReference:
    def __init__(self):
        self.variable = 'Original'
        self.change(self.variable)
        print(self.variable)

    def change(self, var):
        var = 'Changed'

Чи є щось, що я можу зробити, щоб передати змінну за фактичною посиланням?


2055
2018-06-12 10:23


походження


Для короткого пояснення / роз'яснення подивіться першу відповідь на це запитання стосовнопотоків. Оскільки рядки незмінні, вони не будуть змінені, і буде створено нову змінну, отже, "зовнішня" змінна має однакове значення. - PhilS
Код в відповіді БлерКонрада хороший, але пояснення, надані Давидом Крунпауу і ДаренТомасом, є правильним. - Ethan Furman
Перш ніж прочитати вибраний варіант відповіді, будь-ласка, прочитайте цей короткий текст Інші мови мають "змінні", Python має "імена". Подумайте про "імена" та "об'єкти" замість "змінні" та "посилання", і вам слід уникати багатьох подібних проблем. - lqc
Чому це необов'язково використовує клас? Це не Java: P - Peter R
Інший спосіб полягає в тому, щоб створити заголовок "посилання" таким чином: ref = type ('', (), {'n': 1}) stackoverflow.com/a/1123054/409638 - robert


Відповіді:


Аргументи є пройшов за дорученням. Обгрунтування цього є двояко:

  1. параметр, що передається, насправді є а довідка до об'єкта (але посилання передається за значенням)
  2. деякі типи даних мінливі, але інші не є

Так:

  • Якщо ви пройдете а змінний об'єкт у метод, метод отримує посилання на той самий об'єкт, і ви можете перетворити його на насолоду вашому серцю, але якщо ви перепідпорядкуєте посилання в методі, зовнішня сфера не зможе дізнатися про неї, і після того, як ви закінчите, зовнішнє посилання все одно вказує на вихідний об'єкт.

  • Якщо ти пройдеш незмінна об'єкт до методу, ви все ще не можете переконувати зовнішнє посилання, і ви навіть не можете змінити об'єкт.

Щоб зробити це ще більш зрозумілим, давайте мати приклади.

Список - мінливий тип

Давайте спробуємо змінити список, який був переданий методу:

def try_to_change_list_contents(the_list):
    print('got', the_list)
    the_list.append('four')
    print('changed to', the_list)

outer_list = ['one', 'two', 'three']

print('before, outer_list =', outer_list)
try_to_change_list_contents(outer_list)
print('after, outer_list =', outer_list)

Вихід:

before, outer_list = ['one', 'two', 'three']
got ['one', 'two', 'three']
changed to ['one', 'two', 'three', 'four']
after, outer_list = ['one', 'two', 'three', 'four']

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

Тепер давайте подивимося, що відбувається, коли ми намагаємося змінити посилання, яке було передано як параметр:

def try_to_change_list_reference(the_list):
    print('got', the_list)
    the_list = ['and', 'we', 'can', 'not', 'lie']
    print('set to', the_list)

outer_list = ['we', 'like', 'proper', 'English']

print('before, outer_list =', outer_list)
try_to_change_list_reference(outer_list)
print('after, outer_list =', outer_list)

Вихід:

before, outer_list = ['we', 'like', 'proper', 'English']
got ['we', 'like', 'proper', 'English']
set to ['and', 'we', 'can', 'not', 'lie']
after, outer_list = ['we', 'like', 'proper', 'English']

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

Строка - незмінний тип

Це незмінне, тому ми не можемо нічого змінювати зміст рядка

Тепер спробуємо змінити посилання

def try_to_change_string_reference(the_string):
    print('got', the_string)
    the_string = 'In a kingdom by the sea'
    print('set to', the_string)

outer_string = 'It was many and many a year ago'

print('before, outer_string =', outer_string)
try_to_change_string_reference(outer_string)
print('after, outer_string =', outer_string)

Вихід:

before, outer_string = It was many and many a year ago
got It was many and many a year ago
set to In a kingdom by the sea
after, outer_string = It was many and many a year ago

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

Я сподіваюсь, що це трохи почистить.

EDIT: Було зазначено, що це не відповідає на запитання, що з самого початку @David запитав: "Є щось, що я можу зробити, щоб передати змінну фактичною посиланням?". Попрацюємо над цим.

Як ми обійдемо це?

Як показує відповідь @ Andrea, ви можете повернути нове значення. Це не змінює спосіб передачі речей, але дозволяє отримати інформацію, яку ви хочете відновити:

def return_a_whole_new_string(the_string):
    new_string = something_to_do_with_the_old_string(the_string)
    return new_string

# then you could call it like
my_string = return_a_whole_new_string(my_string)

Якщо ви дійсно хочете уникнути використання поверненого значення, ви можете створити клас, щоб зберегти значення, і передати його в функцію або використовувати існуючий клас, як-от список:

def use_a_wrapper_to_simulate_pass_by_reference(stuff_to_change):
    new_string = something_to_do_with_the_old_string(stuff_to_change[0])
    stuff_to_change[0] = new_string

# then you could call it like
wrapper = [my_string]
use_a_wrapper_to_simulate_pass_by_reference(wrapper)

do_something_with(wrapper[0])

Хоча це здається трохи громіздким.


2225
2018-06-12 11:18



Тоді те ж саме в С, коли ви передаєте "за посиланням", ви насправді передаєте за вартістю посилання ... Визначте "за посиланням": P - Andrea Ambu
Я не впевнений, що я розумію ваші умови. Я деякий час вийшов з гри C, але, коли я був у ній, не було "пропуску за посиланням" - ви могли передавати речі, і це завжди було передано за значенням, так що все, що було в списку параметрів було скопійовано Але іноді це було покажчиком, який можна було слідувати до частини пам'яті (примітиву, масив, структуру, що завгодно), але ви не могли змінити покажчик, який був скопійований з зовнішньої області - коли ви закінчили функцію , оригінальний покажчик все-таки вказав на ту саму адресу. C ++ ввів посилання, які поводилися по-різному. - Blair Conrad
@Zac Bowling Я насправді не розумію, що те, що ви кажете, є актуальним у практичному сенсі до цієї відповіді. Якщо новачок Python хотів дізнатися про проходження через ref / val, то витяг з цієї відповіді полягає в наступному: 1- ви може використовуйте посилання, яке функція отримує як аргументи, для зміни зовнішнього значення змінної, поки ви не перепризначаєте параметр для посилання на новий об'єкт. 2- Призначення незмінного типу буде завжди створіть новий об'єкт, який порушує посилання, що ви мали до зовнішньої змінної. - Cam Jackson
@CamJackson, вам потрібно кращий приклад - цифри також є незмінними об'єктами в Python. Крім того, чи не буде правдою сказати це будь-який Призначення без субдолів на лівій стороні рівних переведе ім'я на новий об'єкт, незалежно від того, чи це незмінне чи ні? def Foo(alist): alist = [1,2,3]воля ні змінити вміст списку з точки зору абонентів. - Mark Ransom
-1. Показаний код хороший, пояснення щодо того, як це зовсім не правильно. Див. Відповіді Давида Крунпауу або ДаренТомаса на правильні пояснення щодо причин. - Ethan Furman


Проблема виникає через непорозуміння того, які змінні в Python. Якщо ви звикли до більшості традиційних мов, у вас є ментальна модель того, що відбувається у наступній послідовності:

a = 1
a = 2

Ви вірите в це a це місце пам'яті, яке зберігає значення 1, потім оновлюється, щоб зберегти значення 2. Це не так, як все працює в Python. Скоріше a починається як посилання на об'єкт із значенням 1, потім перепризначається як посилання на об'єкт із значенням 2. Ці два об'єкти можуть продовжувати співіснувати навіть a більше не відноситься до першого; насправді вони можуть бути поділені на будь-яку кількість інших посилань в рамках програми.

Коли ви викликаєте функцію з параметром, створюється нова посилання, що відноситься до об'єкта, який пройшов. Це окремо від посилання, яке було використано у виклику функції, тому неможливо оновити цю посилання та зробити її посиланням на новий об'єкт У вашому прикладі:

def __init__(self):
    self.variable = 'Original'
    self.Change(self.variable)

def Change(self, var):
    var = 'Changed'

self.variable є посиланням на рядковий об'єкт 'Original'. Коли ви зателефонуєте Change ви створюєте другу посилання var до об'єкта. Всередині функції ви перепризначаєте посилання var до іншого рядкового об'єкта 'Changed', але посилання self.variable є окремим і не змінюється.

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

def __init__(self):         
    self.variable = ['Original']
    self.Change(self.variable)

def Change(self, var):
    var[0] = 'Changed'

530
2017-11-15 17:45



Добре стислий пояснення. Ваш абзац "Коли ви називаєте функцію ..." є одним з кращих пояснень, які я чув про досить зашифровану фразу: "Параметри функції Python є посиланнями, переданими за значенням". Я думаю, якщо ви розумієте цей абзац самостійно, то все інше, щось подібне, просто має сенс і виходить з цього логічного висновку. Тоді ви просто повинні знати, коли ви створюєте новий об'єкт, і коли ви модифікуєте існуючий. - Cam Jackson
Але як ви можете передати посилання? Я думав, що ви не можете змінити адресу "var", але тепер ваш рядок "Changed" буде зберігатися на адресі "var". Ваш опис змушує здаватися, що "Змінено" і "Оригінал" належать різним місцям пам'яті, а ви просто перемикаєте "var" на іншу адресу. Це правильно? - Glassjawed
@Glassjawed, я думаю, ви отримуєте це. "Змінені" та "Оригінальні" - це два різних рядкових об'єкта в різних адресах пам'яті, а "var" змінюється від вказівки на один до вказівки на інший. - Mark Ransom
це найкраще пояснення і повинно бути прийнятою відповіддю. Короткий, стислий і зрозумілий. Інші монети нові, заплутані терміни, такі як "посилання передається за значенням", "call be object" тощо. Ця відповідь очищає все. Дякую :) - ajay
Використання функції id () допомагає прояснити питання, оскільки це дає зрозуміти, коли Python створює новий об'єкт (так що я думаю, у всякому разі). - Tim Richardson


Я знайшов інші відповіді досить довго і складно, тому я створив цю просту діаграму, щоб пояснити, як Python розглядає змінні та параметри. enter image description here


243
2017-09-04 16:05



Ваша діаграма добре висвітлює глибину концепції, хоча я б не називав інших відповідей довгий і складний. - Bruce Wayne
Дуже елегантне пояснення. Я думаю, що інші відповіді є також довгими і складними. Хороша робота! - kirk.burleson
Неважливо, чи А мінлива, чи ні. Якщо ви призначите щось інше, ніж B А не змінюється. Якщо об'єкт є змінним, ви можете його змінити, точно. Але це не має нічого спільного з призначенням безпосередньо до імені .. - Martijn Pieters♦
Дякуємо за оновлення, набагато краще! Що бентежить більшість людей - це присвоєння підписки; наприклад, B[0] = 2, проти безпосереднього призначення B = 2. - Martijn Pieters♦
"A призначається Б". Хіба це не двозначно? Я думаю, що в звичайному англійському це може означати і те A=B або B=A. - Hatshepsut


Це не прохідна цінність або прохідна довідка - це заклик до об'єкта. Дивіться це, Фредрік Лунд:

http://effbot.org/zone/call-by-object.htm

Ось важлива цитата:

"... змінні [імена] є ні об'єкти; вони не можуть бути позначені іншими змінами або посиланнями на об'єкти ".

У вашому прикладі, коли Change Метод називається - a простір імен для цього створено; і var стає ім'ям в межах цього простору імен для рядкового об'єкта 'Original'. Тоді об'єкт має назву в двох просторів імен. Далі var = 'Changed' зв'язує var до нового рядкового об'єкта, і тому простір імен методу забуває про 'Original'. Нарешті, цей простір імен забутий, а рядок 'Changed' поряд з цим.


220
2018-06-12 12:55



Мені важко купувати. Для мене це так само, як Java, параметри є вказівниками на об'єкти в пам'яті, а ці покажчики передаються через стек або регістри. - Luciano
Це не схоже на Java. Одна з випадків, коли вона не однакова, є незмінними об'єктами. Подумайте про тривіальну функцію lambda x: x. Застосувати це для x = [1, 2, 3] і x = (1, 2, 3). У першому випадку, повернене значення буде копією введення, а в другому - ідентичним. - David Cournapeau
Ні, це так точно як семантика Java для об'єктів. Я не знаю, що ви маєте на увазі: "У першому випадку, повернене значення буде копією вводу та ідентичне у другому випадку". але ця заява, здається, явно невірна. - Mike Graham
Це точно так само, як у Java. Довідники об'єкта передаються за значенням. Кожен, хто думає по-іншому, повинен прикріпити код Python для a swap функція, яка може обмінюватися двома посиланнями, як це: a = [42] ; b = 'Hello'; swap(a, b) # Now a is 'Hello', b is [42] - cayhorstmann
Це точно так само, як і Java, коли ви передаєте об'єкти в Java. Проте Java також має примітиви, які передаються шляхом копіювання значення примітиву. Таким чином, у цьому випадку вони відрізняються. - Claudiu


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

Отже, при передачі списку до функції / методу список присвоюється ім'ям параметра. Додавання до списку призведе до модифікації списку. Перепризначення списку всередині функція не змінить початковий список, оскільки:

a = [1, 2, 3]
b = a
b.append(4)
b = ['a', 'b']
print a, b      # prints [1, 2, 3, 4] ['a', 'b']

Оскільки незмінних типів неможливо змінити, вони здається як передавання значення - передавання int в функцію означає присвоєння int до параметрів функцій. Ви можете будь-коли перепризначити це, але це не змінить значення оригінальних змінних.


142
2018-06-12 12:17



На перший погляд ця відповідь, здається, обходить сторону оригінального питання. Через секунду прочитав, я зрозумів, що це робить це ясно. Хороший підхід до цієї концепції "присвоєння імені" можна знайти тут: Код, як Pythonista: ідіоматичний пітон - Christian Groleau


Еффот (ака Фредрік Лундх) описав змінну передавача стилю Python як call-by-object: http://effbot.org/zone/call-by-object.htm

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

  • Коли ви робите таке завдання, як x = 1000, створюється словник, який відображає рядок "x" у поточному просторі імен до покажчика на цілий об'єкт, що містить тисячу.

  • Коли ви оновлюєте "х" з x = 2000, створюється новий цілий об'єкт, а словник оновлюється, щоб вказувати на новий об'єкт. Стара тисяча об'єктів не змінюється (і може бути або не бути живим залежно від того, що-небудь інше відноситься до об'єкта).

  • Коли ви виконуєте нове завдання, таке як y = x, створений новий словник "y", який вказує на той самий об'єкт, що і запис для "x".

  • Об'єкти, як рядки та цілі числа, є незмінна. Це просто означає, що немає способів змінити об'єкт після його створення. Наприклад, коли цілий об'єкт об'єднує тисячу, це ніколи не зміниться. Математика виконується шляхом створення нових цілих об'єктів.

  • Об'єкти, як-от списки, є змінний. Це означає, що вміст об'єкта може бути змінений будь-яким об'єктом, що вказує на об'єкт. Наприклад, x = []; y = x; x.append(10); print y буде друкувати [10]. Пустий список був створений. Обидва "x" та "y" вказують на той самий список. The додати Метод мутувати (оновлює) об'єкт списку (наприклад, додати запис до бази даних), і результат видно для обох "x" та "y" (так само, як оновлення бази даних буде видно для кожного з'єднання з цією базою даних).

Сподіваюся, що висвітлюється для вас питання.


53
2018-03-29 04:41



Я дуже вдячний, що дізнався про це від розробника. Чи правда, що id() Функція повертає значення покажчика (об'єкт посилання), як показує відповідь pepr? - Honest Abe
@HonestAbe Так, у CPython id () повертає адресу Але в інших пітонах, таких як PyPy і Jython id () це просто унікальний ідентифікатор об'єкта. - Raymond Hettinger
Інші відповіді залишили мене заплутаним і заплутаним. Це все це очистило. Дякую. - shongololo
ця відповідь плюс натяк на використання ідентифікатора (), який показує, коли присвоєння зв'язує існуюче ім'я з новим об'єктом, є найкращим у всьому потоці. - Tim Richardson
Ця відповідь кримінально недооцінена. - wandadars


Технічно Python завжди використовує прохід за контрольними значеннями. Я збираюся повторити моя інша відповідь щоб підтримати моє твердження.

Python завжди використовує перелічені значення. Існує ніякого винятку. Будь-яке призначення переміщення означає копіювання контрольного значення. Без винятку. Будь-яка змінна - це назва, прив'язана до контрольного значення. Завжди

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

Ось приклад, який доводить, що Python використовує проходження за посиланням:

Illustrated example of passing the argument

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

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

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

Довідкові значення приховані в Python. Для зберігання контрольного значення немає явного типу користувача. Тим не менш, ви можете використовувати елемент списку (або елемент у будь-якому іншому відповідному контейнері) як еталонну змінну, оскільки всі контейнери зберігають елементи також як посилання на цільові об'єкти. Іншими словами, елементи насправді не містяться в контейнері - це лише посилання на елементи.


53
2017-09-15 18:53



Фактично це підтверджується його прохід за контрольністю. +1 для цієї відповіді, хоча цей приклад був непоганим. - BugShotGG
Винахід нової термінології (наприклад, "прохід за еталонним значенням" або "виклик за об'єктом" не є корисним). "Виклик за допомогою (value | reference | name)" є стандартними термінами. "довідка" є стандартним терміном. Передача посилань за значенням точно описує поведінку Python, Java та багатьох інших мов, використовуючи стандартну термінологію. - cayhorstmann
@ cayhorstmann: проблема полягає в тому, що Змінної Python не має такої ж термінології, як і в інших мовах. Сюди, зателефонувати за посиланням тут не підходить добре. Крім того, як ти точно визначити термін довідка? Неофіційно шлях Python можна легко описати як проходження адреси об'єкта. Але це не підходить для потенційно розподіленої реалізації Python. - pepr
Мені подобається ця відповідь, але ви можете розглянути, чи дійсно цей приклад дійсно допомагає чи шкодить потоку. Крім того, якщо ви замінили 'reference value' на 'reference reference', то ви будете використовувати термінологію, яку ми можемо вважати «офіційною», як це видно тут: Визначення функцій - Honest Abe
У кінці цієї цитата вказано виноску, яка говорить: "Насправді, викликати за посиланням на об'єкт буде кращим описом, оскільки якщо змінний об'єкт буде переданий, абонент буде бачити будь-які зміни, які callee робить для цього ... " Я згоден з вами, що плутанина викликана намаганням відповідати термінології, встановленої іншими мовами. Семантика в бік, речі, які треба розуміти: словники / простору імен, іменування обов'язкових операцій і співвідношення ім'я → покажчик → об'єкта (як ви вже знаєте). - Honest Abe


Простий трюк, який я зазвичай користуюся, - це просто скласти його в список:

def Change(self, var):
    var[0] = 'Changed'

variable = ['Original']
self.Change(variable)      
print variable[0]

(Так, я знаю, що це може бути незручно, але іноді це просто зробити це).


36
2017-08-05 22:52



+1 для невеликої кількості тексту, що надає важливий шлях вирішення проблеми Python, що не має проміжок часу. (Як наступний коментар / питання, що підходить як тут, так і де-небудь на цій сторінці: для мого не зрозуміло, чому пітон не може надати ключове слово "ref", як-от C #, що просто обгортає аргумент абонента у вигляді списку це, і розглядати посилання на аргумент у межах функції як 0-го елемента списку.) - M Katz
Приємно Пройти реф, обернути в []. - Justas
Відмінно, добре зроблено, щоб уникнути всіх понтифікацій. - Puffin