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


2628

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

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

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

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


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

10
Код у відповіді БлерКонрада хороший, але пояснення, надані ДевідКурнопо та ДаренТомас, є правильним.
Етан Фурман

70
Перш ніж прочитати обрану відповідь, будь ласка, врахуйте, читай цей короткий текст. Інші мови мають "змінні", Python має "імена" . Подумайте про "імена" та "об'єкти" замість "змінних" та "посилань", і вам слід уникати безлічі подібних проблем.
lqc

24
Чому для цього не потрібно використовувати клас? Це не Java: P
Peter R

2
інше вирішення полягає у створенні обгортки 'посилання', як це: ref = type ('', (), {'n': 1}) stackoverflow.com/a/1123054/409638
robert

Відповіді:


2830

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

  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_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_stringбула копія outer_stringпосилання, і ми the_stringвказували на новий рядок, але змінити місце, де outer_stringвказано, не було.

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

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

Як ми можемо обійти це?

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

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])

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


165
Тоді те саме в C, коли ви переходите "за посиланням", ви насправді проїжджаєте за значенням посилання ... Визначте "за посиланням": P
Андреа Амбу

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

34
@Zac Bowling Я не дуже розумію, наскільки ця відповідь, у практичному розумінні, стосується цієї відповіді. Якщо новачок Python хотів знати про проходження повз ref / val, то виведення з цієї відповіді: 1- Ви можете використовувати посилання, яке функція отримує в якості аргументів, для зміни значення "зовнішньої" змінної, доки так як ви не перепризначите параметр для посилання на новий об'єкт. 2- Призначення непорушного типу завжди створюватиме новий об’єкт, який порушує посилання, яке було у вас на зовнішню змінну.
Cam Jackson

11
@CamJackson, вам потрібен кращий приклад - числа також є незмінними об'єктами в Python. Крім того, чи не правда сказати, що будь-яке завдання без підписки на лівій частині рівня дорівнює перейменуванню імені новому об'єкту, чи він непорушний, чи ні? def Foo(alist): alist = [1,2,3]буде НЕ змінювати вміст списку з точки зору абонентів.
Марк Викуп

59
-1. Показаний код хороший, пояснення, як це абсолютно неправильно. Дивіться відповіді ДевідКурнапу чи ДаренТомаса, щоб отримати правильні пояснення щодо того, чому.
Етан Фурман

685

Проблема виникає з нерозуміння того, які змінні є в 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'

106
Хороше коротке пояснення. Ваш абзац "Коли ви викликаєте функцію ..." - це одне з найкращих пояснень, які я чув, настільки виразну фразу, що "Параметри функції Python - це посилання, передані за значенням". Я думаю, якщо ви розумієте лише цей абзац, все інше щось має сенс і звідти випливає як логічний висновок. Тоді ви просто повинні бути в курсі, коли ви створюєте новий об’єкт і коли ви змінюєте існуючий.
Cam Jackson

3
Але як можна переназначити посилання? Я думав, що ти не можеш змінити адресу 'var', але те, що твій рядок "Змінено" тепер буде зберігатися в адресі пам'яті "var". З вашого опису здається, що "Змінено" та "Оригінал" належать до різних місць у пам'яті, і ви просто переключите "var" на іншу адресу. Це правильно?
Склопакова

9
@Glassjawed, я думаю, ти це отримуєш. "Змінено" та "Оригінал" - це два різних рядкових об'єкти за різними адресами пам'яті та "var" змінюється від вказівки до одного до вказування на інший.
Марк Рансом

використання функції id () допомагає з’ясувати питання, оскільки це дає зрозуміти, коли Python створює новий об’єкт (так я думаю, все одно).
Тім Річардсон

1
@MinhTran найпростіше, посилання - це те, що "посилається" на об'єкт. Фізичне уявлення про це, швидше за все, вказівник, але це просто деталізація реалізації. Це насправді абстрактне поняття в основі.
Марк Рансом

329

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


2
прекрасний, дозволяє легко помітити тонкий розбіг, що існує проміжне призначення, не очевидне для випадкового глядача. +1
користувач22866

9
Не має значення, змінюється чи ні. Якщо Ви призначите щось інше B, A не змінюється . Якщо об'єкт є мутаційним, його можна обов'язково мутувати. Але це не має нічого спільного з присвоєнням прямо імені ..
Martijn Pieters

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

4
Дякую за оновлення, набагато краще! Що бентежить більшість людей - це призначення передплати; наприклад B[0] = 2, по порівнянні з прямим призначенням, B = 2.
Martijn Pieters

11
"A призначено B." Хіба це не неоднозначно? Я думаю, що в звичайній англійській мові це може означати A=Bабо B=A.
Хатшепсут

240

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

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

Ось вагома цитата:

"... змінні [імена] не є об'єктами; їх не можна позначати іншими змінними або називати об'єктами."

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


21
Мені важко придбати. Для мене так само, як і Java, параметри - це вказівники на об'єкти в пам'яті, і ці вказівники передаються через стек або регістри.
Лучано

10
Це не так, як java. Один із випадків, коли це не те саме, - це незмінні предмети. Подумайте про тривіальну функцію лямбда x: x. Застосуйте це для x = [1, 2, 3] і x = (1, 2, 3). У першому випадку повернене значення буде копією введення, а у другому - ідентичним.
Девід Курнопо

26
Ні, це точно як семантика Java для об'єктів. Я не впевнений, що ви маєте на увазі під "У першому випадку повернене значення буде копією введення, а у другому - ідентичним". але це твердження здається явно невірним.
Майк Грехем

23
Це точно так само, як у Java. Посилання на об'єкти передаються за значенням. Кожен, хто думає по-іншому, повинен приєднати код Python для swapфункції, яка може поміняти дві посилання, як-от так: a = [42] ; b = 'Hello'; swap(a, b) # Now a is 'Hello', b is [42]
cayhorstmann

24
Це точно так само, як Java, коли ви передаєте об'єкти в Java. Однак у Java також є примітиви, які передаються шляхом копіювання значення примітиву. Таким чином, вони відрізняються в цьому випадку.
Клавдіу

174

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

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

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

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


3
На перший погляд ця відповідь, здається, убік від початкового питання. Після другого прочитання я зрозумів, що це робить справу досить зрозумілою. Тут можна знайти хороший досвід
Christian Groleau

65

Effbot (він же Фредрік Лунд) описав змінний стиль проходження Python як виклик за об'єктом: http://effbot.org/zone/call-by-object.htm

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

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

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

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

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

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

Сподіваємось, що з’ясує це питання для вас.


2
Я дуже вдячний дізнатися про це від розробника. Чи правда, що id()функція повертає значення вказівника (посилання на об'єкт), як підказує відповідь pepr?
Чесний Абе

4
@HonestAbe Так, у CPython id () повертає адресу. Але в інших пітонах, таких як PyPy та Jython, id () - просто унікальний ідентифікатор об'єкта.
Реймонд Хеттінгер

59

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

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

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

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

Ілюстрований приклад передачі аргументу

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

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

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

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


1
Насправді це підтверджено його проходження за контрольним значенням. +1 за цю відповідь, хоча приклад не був хорошим.
BugShotGG

30
Винайдення нової термінології (наприклад, "пройти за опорним значенням" або "зателефонувати за об'єктом" не є корисним). "Дзвінок за (значення | посилання | ім'я)" - це стандартні умови. "довідник" - це стандартний термін. Передача посилань за значенням точно описує поведінку Python, Java та безлічі інших мов, використовуючи стандартну термінологію.
cayhorstmann

4
@cayhorstmann: Проблема полягає в тому, що змінна Python має не те саме термінологічне значення, як в інших мовах. Таким чином, дзвінок за посиланням тут не підходить добре. Крім того, як ви точно визначаєте термін посилання ? Неофіційно спосіб Python може бути легко описаний як передача адреси об'єкта. Але це не відповідає потенційно розподіленій реалізації Python.
pepr

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

2
В кінці цієї цитати є виноска, яка зазначає: "Насправді, дзвінок за посиланням на об'єкт був би кращим описом, оскільки якщо буде передано змінний об'єкт, абонент побачить будь-які зміни, які викликає в нього виклик ... " Я погоджуюся з вами, що плутанина викликається спробою пристосувати термінологію, встановлену іншими мовами. У семантиці окрім речей, які потрібно розуміти: словники / простори імен, операції зв’язування імен та співвідношення імені → вказівник → об’єкт (як ви вже знаєте).
Чесний Абе

56

У Python змінних немає

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

  1. У Python є імена та об'єкти.
  2. Призначення прив'язує ім'я до об'єкта.
  3. Передача аргументу у функцію також прив'язує ім'я (ім'я параметра параметра) до об'єкта.

Це все, що там є. Зміна цього питання не має значення для цього питання.

Приклад:

a = 1

Це пов'язує ім'я aз об'єктом типу цілого числа, який містить значення 1.

b = x

Це пов'язує ім'я bз тим самим об'єктом, до якого ім'я xв даний час пов'язане. Після цього ім'я bвже не має нічого спільного з ім'ям x.

Дивіться розділи 3.1 та 4.2 у посиланні на мову Python 3.

Як прочитати приклад у питанні

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

Як пройти посилання

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

Якщо це незмінний об'єкт (наприклад, bool, число, рядок), шлях слід загорнути в об'єкт, що змінюється.
Швидке і брудне рішення для цього - це одноелементний список (замість того self.variable, щоб передати [self.variable]та змінити функцію var[0]).
Більш пітонічним підходом було б запровадити тривіальний клас з однозначними ознаками. Функція отримує екземпляр класу і маніпулює атрибутом.


20
"Python не має змінних" - це дурне і заплутане гасло, і я дуже хочу, щоб люди перестали його говорити ... :( Решта цієї відповіді - це добре!
Нед Батчелдер,

9
Це може шокувати, але це не дурно. І я не думаю, що це також бентежить: це, сподіваємось, відкриває розум одержувача для пояснень, що надходять, і ставить її до корисного ставлення "Цікаво, що вони мають замість змінних". (Так, пробіг може змінюватися.)
Lutz Prechelt

13
Ви також сказали б, що у Javascript немає змінних? Вони працюють так само, як і Python. Крім того, Java, Ruby, PHP, .... Я думаю, що краща методика викладання полягає в тому, що "змінні Python працюють інакше, ніж C".
Нед Батчелдер,

9
Так, у Java є змінні. Так само і Python, і JavaScript, Ruby, PHP тощо. Ви не казали б на Java, що intоголошує змінну, але Integerні. Вони обидва оголошують змінні. IntegerЗмінна є об'єктом, то intзмінна є примітивним. Як приклад, ви продемонстрували, як працюють ваші змінні, показавши a = 1; b = a; a++ # doesn't modify b. Це точно так само і в Python (використовуючи, += 1оскільки ++в Python немає )!
Нед Батчелдер

1
Поняття "змінна" є складним і часто розпливчастим: Змінна - це контейнер для значення, ідентифікованого іменем. У Python значення - це об'єкти, контейнери - це об'єкти (бачите проблему?), А імена - це фактично окремі речі. Я вважаю, що набагато складніше отримати чітке розуміння змінних таким чином. Пояснення імен та об'єктів видається складнішим, але насправді простішим.
Lutz Prechelt

43

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

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

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

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


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

5
Приємно. Щоб пройти повз, загорніть у [].
Юстас

38

(редагувати - Блер оновив свою надзвичайно популярну відповідь, щоб вона тепер була точною)

Я думаю, що важливо відзначити, що нинішня посада з найбільшою кількістю голосів (Блер Конрад), хоча і є коректною щодо її результату, вводить в оману та є невірно обмеженою на основі її визначень. Хоча існує багато мов (наприклад, C), які дозволяють користувачеві пройти посилання або передати значення, Python не є однією з них.

Відповідь Девіда Курнопо вказує на реальну відповідь і пояснює, чому поведінка на посаді Блера Конрада здається правильною, а визначення - ні.

Наскільки Python передається за значенням, всі мови передаються за значенням, оскільки має бути надіслана частина даних (будь то "значення" або "посилання"). Однак це не означає, що Python передається за значенням у тому сенсі, що програміст на C подумає про це.

Якщо ви хочете поведінки, відповідь Блера Конрада чудова. Але якщо ви хочете дізнатися гайки та болти, чому Python не є ні прохідним, ні посиланням, прочитайте відповідь Девіда Курно.


4
Просто не вірно, що всі мови називаються за значенням. У C ++ або Pascal (і, напевно, у багатьох інших, яких я не знаю), ви телефонуєте за посиланням. Наприклад, в C ++ void swap(int& x, int& y) { int temp = x; x = y; y = temp; }буде підміняти змінні, передані йому. У Паскалі ви використовуєте varзамість &.
cayhorstmann

2
Я думав, що відповів на це давно, але не бачу цього. Для повноти - Кайхорстман неправильно зрозумів мою відповідь. Я не говорив, що все називається за значенням в термінах, які більшість людей вперше дізнаються щодо C / C ++ . Просто було передано якесь значення (значення, ім'я, вказівник тощо), і терміни, використані в оригінальній відповіді Блера, були неточними.
KobeJohn

24

Тут ви отримали кілька справді хороших відповідей.

x = [ 2, 4, 4, 5, 5 ]
print x  # 2, 4, 4, 5, 5

def go( li ) :
  li = [ 5, 6, 7, 8 ]  # re-assigning what li POINTS TO, does not
  # change the value of the ORIGINAL variable x

go( x ) 
print x  # 2, 4, 4, 5, 5  [ STILL! ]


raw_input( 'press any key to continue' )

2
так, однак якщо ви робите x = [2, 4, 4, 5, 5], y = x, X [0] = 1, друкуйте x # [1, 4, 4, 5, 5] друкуйте y # [1 , 4, 4, 5, 5]
laycat

19

У цьому випадку змінній, названій varу методі Change, присвоюється посилання self.variable, і ви негайно присвоюєте рядок var. Це вже не вказує self.variable. Наведений нижче фрагмент коду показує, що станеться, якщо ви зміните структуру даних, на яку вказує, varі self.variableв цьому випадку список:

>>> class PassByReference:
...     def __init__(self):
...         self.variable = ['Original']
...         self.change(self.variable)
...         print self.variable
...         
...     def change(self, var):
...         var.append('Changed')
... 
>>> q = PassByReference()
['Original', 'Changed']
>>> 

Я впевнений, що хтось ще може це прояснити далі.


18

Схема передачі по призначенню Python не зовсім така, як параметр посилальних параметрів C ++, але на практиці вона дуже схожа на модель передачі аргументів мови С (та інших):

  • Невідмінні аргументи ефективно передаються « за значенням ». Об'єкти, такі як цілі числа та рядки, передаються посиланнями на об'єкти замість копіювання, але оскільки ви не можете змінити незмінні об'єкти на місці, ефект схожий на копіювання.
  • Змінні аргументи ефективно передаються " за вказівником ". Об'єкти, такі як списки та словники, також передаються посиланнями на об'єкти, що подібне до того, як C передає масиви як покажчики - змінні об'єкти можуть бути змінені на місці функції, подібно до масивів C.

16

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

http://docs.python.org/3/faq/programming.html#what-are-the-rules-for-local-and-global-variables-in-python

приклад:

>>> def x(y):
...     global z
...     z = y
...

>>> x
<function x at 0x00000000020E1730>
>>> y
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'y' is not defined
>>> z
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'z' is not defined

>>> x(2)
>>> x
<function x at 0x00000000020E1730>
>>> y
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'y' is not defined
>>> z
2

5
Мене спокусило опублікувати подібну відповідь - оригінальний запитувач, можливо, не знав, що те, що він хотів, насправді використовувати глобальну змінну, поділену між функціями. Ось посилання я розділив би: stackoverflow.com/questions/423379 / ... У відповідь на @ Тім, переповнення стека не тільки питання і відповідь на сайті, це величезне сховище довідкової знань , що стає тільки сильніше і більш nuanced- багато як активна вікі - з більшою кількістю інформації.
Max P Magee

13

Тут багато розумінь у відповідях, але, думаю, додатковий пункт тут чітко не згадується. Цитування з python документації https://docs.python.org/2/faq/programming.html#what-are-the-rules-for-local-and-global-variables-in-python

"У Python змінні, на які посилається лише функція, неявно глобальні. Якщо змінній присвоєно нове значення в будь-якому місці в тілі функції, вона вважається локальною. Якщо змінній колись присвоюється нове значення всередині функції, змінна неявно локальна, і вам потрібно явно оголосити її як "глобальну". Хоча спочатку це трохи дивно, це врахування пояснює це. З одного боку, вимога глобального для призначених змінних забезпечує смугу проти непередбачуваних побічних ефектів. з іншого боку, якщо глобальний був необхідний для всіх глобальних посилань, ви б весь час використовували глобальний. Вам потрібно було б оголосити як глобальне кожне посилання на вбудовану функцію або на компонент імпортного модуля. загрожує корисності глобальної декларації для виявлення побічних ефектів ".

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

def test(l):
    print "Received", l , id(l)
    l = [0, 0, 0]
    print "Changed to", l, id(l)  # New local object created, breaking link to global l

l= [1,2,3]
print "Original", l, id(l)
test(l)
print "After", l, id(l)

дає:

Original [1, 2, 3] 4454645632
Received [1, 2, 3] 4454645632
Changed to [0, 0, 0] 4474591928
After [1, 2, 3] 4454645632

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


10

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

def change_me(list):
   list = [1, 2, 3]

my_list = [0, 1]
change_me(my_list)

Фактичний об’єкт - [0, 1] (який би називався значенням в інших мовах програмування) передається. Тому насправді функція change_meнамагатиметься зробити щось на кшталт:

[0, 1] = [1, 2, 3]

що, очевидно, не змінить об'єкт, переданий функції. Якщо функція виглядала так:

def change_me(list):
   list.append(2)

Тоді виклик призведе до:

[0, 1].append(2)

що, очевидно, змінить об’єкт. Ця відповідь це добре пояснює.


2
Проблема полягає в тому, що доручення робить щось інше, ніж ви очікували. У list = [1, 2, 3]причинах реіспользованія на listім'я чого - то ще , і забуваючи спочатку переданий об'єкт. Однак ви можете спробувати list[:] = [1, 2, 3](до речі list, неправильна назва змінної. Думаючи про[0, 1] = [1, 2, 3] це - повна нісенітниця. У будь-якому випадку, що ви думаєте, означає, що сам об'єкт передається ? Що скопійовано у вашу функцію?
pepr

Об'єкти @pepr не є літералами. Вони є об'єктами. Єдиний спосіб поговорити про них - дати їм деякі імена. Ось чому це так просто, коли ви це зрозумієте, але надзвичайно складно пояснити. :-)
Векі

@Veky: Я це знаю. У будь-якому випадку, літеральний список перетворюється на об'єкт списку. Насправді, будь-який об’єкт в Python може існувати без імені, і його можна використовувати навіть тоді, коли йому не надано жодного імені. І ви можете думати про них, як про анонімні об’єкти. Подумайте, як предмети є елементами списків. Їм не потрібно імені. Ви можете отримати доступ до них через індексацію або повторення через список. У будь-якому випадку, я наполягаю, [0, 1] = [1, 2, 3]це просто поганий приклад. У Python нічого подібного немає.
пепр

@pepr: Я не обов'язково маю на увазі імена визначення Python, а лише звичайні імена. Звичайноalist[2] вважається назвою третього елемента алісту. Але я думаю, що я неправильно зрозумів, у чому полягає ваша проблема. :-)
Векі

Арг. Моя англійська, очевидно, набагато гірша, ніж мій Python. :-) Спробую ще раз. Я щойно сказав, що ви повинні дати об’єкту деякі імена, щоб просто поговорити про них. Під цим "іменами" я не мав на увазі "імена, визначені Python". Я знаю механізми Python, не хвилюйтеся.
Веки

8

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

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

    def Change(self):
        self.variable = 'Changed'

У методах, наприклад, ви зазвичай посилаєтесь selfна доступ до атрибутів екземпляра. Зазвичай встановлювати атрибути екземплярів __init__і читати чи змінювати їх в методах екземплярів. Ось чому ви також передаєте selfперший аргументdef Change .

Іншим рішенням було б створити статичний метод на зразок цього:

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

    @staticmethod
    def Change(var):
        var = 'Changed'
        return var

6

Існує невелика хитрість передавати об’єкт за посиланням, навіть якщо мова не робить це можливим. Він працює і на Java, це список з одним елементом. ;-)

class PassByReference:
    def __init__(self, name):
        self.name = name

def changeRef(ref):
    ref[0] = PassByReference('Michael')

obj = PassByReference('Peter')
print obj.name

p = [obj] # A pointer to obj! ;-)
changeRef(p)

print p[0].name # p->name

Це некрасивий хакер, але він працює. ;-P


pє посиланням на об'єкт, що змінюється, що списку, який, в свою чергу, зберігає об'єкт obj. Посилання 'p' переходить у форму changeRef. Всередині changeRefстворюється нова посилання (нова посилання називається ref), яка вказує на той самий об’єкт списку, на який pвказує. Але оскільки списки є змінними, зміни в списку видно обома посиланнями. У цьому випадку ви використовували refпосилання для зміни об'єкта в індексі 0, щоб він згодом зберігав PassByReference('Michael')об'єкт. Зміна об’єкта списку була здійснена за допомогою, refале ця зміна видима p.
Мінь Тран

Отже, посилання pі refвказують на об'єкт списку , який зберігає один об'єкт, PassByReference('Michael'). Тож випливає, що p[0].nameповертається Michael. Зрозуміло, refзараз він вийшов із сфери застосування та може збирати сміття, але все одно.
Мінь Тран

Однак ви не змінили змінну приватного екземпляра nameоригінального PassByReferenceоб'єкта, пов'язаного з посиланням obj. Насправді obj.nameповернеться Peter. Вищезгадані коментарі передбачають дане визначення Mark Ransom.
Мінь Тран

По суті, я не погоджуюся, що це хак (що я маю на увазі, щоб посилатися на щось, що працює, але з причин, невідомих, неперевірених або ненавмисних реалізатором). Ви просто замінили один PassByReferenceоб’єкт іншим PassByReferenceоб’єктом у своєму списку і посилалися на останній із двох об’єктів.
Мінь Тран

5

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

a=0
b=0
c=0
def myfunc(a,b,c):
    a=1
    b=2
    c=3
    return a,b,c

a,b,c = myfunc(a,b,c)
print a,b,c

Так, це вирішує "пройти посилання" і в моєму випадку використання. У мене є функція, яка в основному очищає значення в adict а потім повертає dict. Однак під час очищення може здатися, що потрібно відновити частину системи. Тому функція повинна не тільки повертати очищене, dictале й бути здатним сигналізувати про відновлення. Я намагався пройти boolпосилання, але часто це не працює. Зрозумівши, як вирішити це, я знайшов, що ваше рішення (в основному повернення кортежу) працює найкраще, а також взагалі не є злом / обхід (IMHO).
Казимир

3

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

Основна ідея - це функція, яка може робити цей доступ і може бути передана як об'єкт в інші функції або збережена в класі.

Один із способів - використання global(для глобальних змінних) або nonlocal(для локальних змінних у функції) у функції обгортки.

def change(wrapper):
    wrapper(7)

x = 5
def setter(val):
    global x
    x = val
print(x)

Ця ж ідея працює для читання та delвикористання змінної.

Для простого читання існує ще коротший спосіб простого використання, lambda: xякий повертає дзвінок, який при виклику повертає поточне значення x. Це дещо схоже на "дзвінок по імені", що використовується в мовах у далекому минулому.

Передати 3 обгортки для доступу до змінної трохи непросто, тому їх можна перетворити в клас, який має атрибут проксі:

class ByRef:
    def __init__(self, r, w, d):
        self._read = r
        self._write = w
        self._delete = d
    def set(self, val):
        self._write(val)
    def get(self):
        return self._read()
    def remove(self):
        self._delete()
    wrapped = property(get, set, remove)

# left as an exercise for the reader: define set, get, remove as local functions using global / nonlocal
r = ByRef(get, set, remove)
r.wrapped = 15

Підтримка «відображення» пітонів дає можливість отримати об’єкт, здатний перепризначити ім’я / змінну в заданій області, не визначаючи функції прямо в цій області:

class ByRef:
    def __init__(self, locs, name):
        self._locs = locs
        self._name = name
    def set(self, val):
        self._locs[self._name] = val
    def get(self):
        return self._locs[self._name]
    def remove(self):
        del self._locs[self._name]
    wrapped = property(get, set, remove)

def change(x):
    x.wrapped = 7

def test_me():
    x = 6
    print(x)
    change(ByRef(locals(), "x"))
    print(x)

Тут ByRefклас закриває доступ до словника. Тож доступ до атрибутів wrappedперекладається на доступ до елемента в пройденому словнику. Передаючи результат вбудованого localsі ім'я локальної змінної, це закінчується доступ до локальної змінної. Документація python на 3.5 повідомляє, що зміна словника може не працювати, але, здається, працює для мене.


3

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

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

    def change(self, var):
        self.__dict__[var] = 'Changed'

у реальному коді ви, звичайно, додали б перевірку помилок під час пошуку dict.


3

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

# returns the result of adding numbers `a` and `b`
def AddNumbers(a, b, ref): # using a dict for reference
    result = a + b
    ref['multi'] = a * b # reference the multi. ref['multi'] is number
    ref['msg'] = "The result: " + str(result) + " was nice!" # reference any string (errors, e.t.c). ref['msg'] is string
    return result # return the sum

number1 = 5
number2 = 10
ref = {} # init a dict like that so it can save all the referenced values. this is because all dictionaries are passed by reference, while strings and numbers do not.

sum = AddNumbers(number1, number2, ref)
print("sum: ", sum)             # the return value
print("multi: ", ref['multi'])  # a referenced value
print("msg: ", ref['msg'])      # a referenced value

3

Pass-By-Reference у Python сильно відрізняється від концепції передачі посилань у C ++ / Java.

  • Java & C #: примітивні типи (включають рядок) передають за значенням (копія), Тип посилання передається за посиланням (копія адреси), тому всі зміни, внесені в параметр у виклику функції, видимі абоненту.
  • C ++: Дозволені як прохідні посилання, так і значення пропускання. Якщо параметр передається за посиланням, ви можете його змінити чи не залежно від того, передано параметр як const чи ні. Однак, const чи ні, параметр підтримує посилання на об'єкт, а посилання не може бути призначене для вказівки на інший об'єкт у межах викликаної функції.
  • Python: Python - це "посилання на об'єкт-посилання", про який часто говорять: "Посилання на об'єкти передаються за значенням". [Читайте тут] 1. І абонент, і функція посилаються на один і той же об'єкт, але параметр у функції є новою змінною, яка просто містить копію об'єкта в абонента. Як і C ++, параметр може бути модифікованим або не функціонувати - це залежить від типу переданого об'єкта. наприклад; Тип незмінного об'єкта не може бути змінений у виклику функції, тоді як об'єкт, що змінюється, може бути оновлений або повторно ініціалізований. Важливою відмінністю між оновленням або повторним присвоєнням / повторною ініціалізацією змінної змінної є те, що оновлене значення відбивається назад у викликаній функції, тоді як реініціалізоване значення не має. Обсяг будь-якого присвоєння нового об'єкта змінній змінній є локальним для функції в пітоні. Приклади, надані @ blair-conrad, чудово розуміють це.

1
Старий, але я відчуваю обов'язок це виправити. Рядки передаються за посиланням як на Java, так і на C #, а не за значенням
Джон,

2

Оскільки ваш приклад є об'єктно-орієнтованим, ви можете внести такі зміни, щоб досягти подібного результату:

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

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

# o.variable will equal 'Changed'
o = PassByReference()
assert o.variable == 'Changed'

1
Хоча це працює. Це не пройти шляхом посилання. Це "пройти посилання на об'єкт".
Bishwas Mishra

1

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

class RefsObj(object):
    "A class which helps to create references to variables."
    pass

...

# an example of usage
def change_ref_var(ref_obj):
    ref_obj.val = 24

ref_obj = RefsObj()
ref_obj.val = 1
print(ref_obj.val) # or print ref_obj.val for python2
change_ref_var(ref_obj)
print(ref_obj.val)

1

Оскільки, здається, ніде не згадано, підхід до імітації посилань, як відомо, наприклад, C ++, полягає у використанні функції "оновлення" та передачі її замість фактичної змінної (а точніше, "імені"):

def need_to_modify(update):
    update(42) # set new value 42
    # other code

def call_it():
    value = 21
    def update_value(new_value):
        nonlocal value
        value = new_value
    need_to_modify(update_value)
    print(value) # prints 42

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

Очевидно, що вищесказане не дозволяє прочитати значення, а лише оновити його.

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