Функції Python викликають за посиланням


84

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

Приклад 1:

k = 2

def foo (n):
     n = n * n     #clarity regarding comment below
     square = n
     return square

j = foo(k)
print j
print k

показав би

>>4
>>2

показуючи, що k не змінюється

У цьому прикладі змінна n ніколи не змінюється

Приклад 2:

n = 0
def foo():
    global n
    n = n * n
    return n

У цьому прикладі змінна n змінена

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

По-друге, на іспитах в Кембриджі рівня A вони зараз кажуть, що функція повертає одне значення, тоді як процедура повертає більше одного значення. Мене навчили, що функція має оператор return, а процедура - ні, протягом 80-х. Чому це зараз неправильно?


5
Найкращий ресурс, який я знайшов для розуміння моделі виклику python, - це стаття на effbot: effbot.org/zone/call-by-object.htm
Wilduck,

1
ви повинні прочитати про змінні Python, змінні та незмінні об'єкти. Також у вашому першому прикладі, чому б це nбуло змінено, ви використовуєте нову змінну squareдля зберігання результату вашого розрахунку.
Facundo Casco

6
Будь ласка, не публікуйте два не пов’язані між собою запитання в одному. Визначення функції проти процедури - це питання визначення, і, схоже, ваше визначення упереджене до Паскаля. У жаргоні Python не існує такої речі, як процедура.
Fred Foo

2
Крім того, подивіться на цю попередню відповідь StackOverflow. stackoverflow.com/a/10262945/173292 Він досить інтуїтивно пояснює виклик за допомогою еталонної моделі об'єкта.
Wilduck

1
Дивіться також це запитання SO для дуже корисного пояснення: як мені передати змінну за посиланням
Michael Ohlrogge

Відповіді:


79

Ви не можете змінити незмінний об'єкт, наприклад, strабо tuple, всередині функції в Python, але ви можете зробити такі речі:

def foo(y):
  y[0] = y[0]**2

x = [5]
foo(x)
print x[0]  # prints 25

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

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

def foo(x, y):
   return x**2, y**2

a = 2
b = 3
a, b = foo(a, b)  # a == 4; b == 9

Коли ви повертаєте такі значення, вони повертаються як Кортеж, який, в свою чергу, розпаковується.

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

def clear_a(x):
  x = []

def clear_b(x):
  while x: x.pop()

z = [1,2,3]
clear_a(z) # z will not be changed
clear_b(z) # z will be emptied

2
Було б корисно переглянути результати програм. Наприклад, чи повертає 'print x [0]' 25? (Так, але це слід показати.)
Рей Салемі

11
@alexey clear_aотримує посилання zта зберігає це посилання у x. Потім він негайно змінюється xна посилання на інший масив. Оригінальний zмасив забутий у межах clear_a, але насправді не змінюється. Він повертається до глобального масштабу без змін. Порівняйте з тим, clear_bщо бере посилання на нього, zа потім діє безпосередньо на нього, ніколи не створюючи нового масиву або вказуючи іншим чином xі щось інше.
dave mankoff

спасибі Дейве! Я намагався «створити новий масив» в clear_b, y = x, y: y.pop()а потім називається clear_b(z), г ще отримав спорожніли ... Так що нам потрібно що - щось на зразок y = list(x)створити копію х (список (х) пояснив тут )
алексей

run_thread = [True] t= threading.Thread(target=module2.some_method, \ args=(11,1,run_thread)) --do something - and when you want to sop run_thread[0] =False
Алекс Пуннен

У Python немає примітивного типу, на відміну від Java. Екземпляри об'єктів усіх типів.
Марко Сулла

184

По суті, існує три види `` викликів функцій '':

  • Передайте значення
  • Передайте посилання
  • Передайте посилання на об’єкт

Python - мова програмування PASS-BY-OBJECT-REFERENCE.

По-перше, важливо розуміти, що змінна та значення змінної (об'єкта) - це дві окремі речі. Змінна "вказує" на об'єкт. Змінна не є об’єктом. Ще раз:

ЗМІННА НЕ ОБ'ЄКТ

Приклад: у наступному рядку коду:

>>> x = []

[]є порожнім списком, xє змінною, яка вказує на порожній список, але xсама не є порожнім списком.

Розгляньте змінну ( x, у наведеному вище випадку) як поле, а 'значення' змінної ( []) як об’єкт усередині поля.

ПРОХОДИТИ ЗА ДОСИЛАННЯМ ОБ'ЄКТУ (регістр на пітоні):

Тут "Посилання на об'єкти передаються за значенням".

def append_one(li):
    li.append(1)
x = [0]
append_one(x)
print x

Тут оператор x = [0]робить змінну x(поле), яка вказує на об'єкт [0].

На викликаній функції створюється нове поле li. Вміст li- ТЕ ЖЕ, що і вміст вікна x. Обидва поля містять однаковий об'єкт. Тобто обидві змінні вказують на один і той же об’єкт у пам’яті. Отже, будь-яка зміна об'єкта, на який вказує, liтакож відображатиметься на об'єкті, на який вказує x.

На закінчення, результатом роботи вищезазначеної програми буде:

[0, 1]

Примітка:

Якщо змінна liперепризначена у функції, тоді liбуде вказувати на окремий об'єкт у пам'яті. xоднак продовжить вказувати на той самий об'єкт в пам'яті, на який вказував раніше.

Приклад:

def append_one(li):
    li = [0, 1]
x = [0]
append_one(x)
print x

Результатом роботи програми буде:

[0]

ПРОХОДИТИ ЗА ДОВІДКАМИ:

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

ПРОХОДИТИ ЗА ЗНАЧЕННЯМ:

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

Сподіваюся, це допомагає.


4
Не могли б ви навести приклади "передавати посилання" та "передавати значення", як це було зроблено для "передавати посилання на об'єкт".
TJain

@TJain Передати посилання та передати значення можна побачити в C ++. Ця відповідь дає гарне пояснення: stackoverflow.com/a/430958/5076583
Shobhit Verma

3
Я досі не розумію, чим це відрізняється від передачі-посилання. Ця відповідь відчайдушно потребує прикладу того, як ідентичний псевдокод дасть різні результати для всіх 3 парадигм. Посилання на іншу відповідь, яка лише пояснює різницю між передачею за посиланням та передачею значення, не допомагає.
Jess Riedel

Чи не простіше сказати, що "всі змінні в Python є покажчиками " і "всі змінні, призначені параметрам у виклику функції, присвоюються новим покажчикам?". Якщо ви це зрозумієте, ви побачите, що Python схожий на Java: він просто завжди передає покажчики за значенням . Дивіться відповідь Кірка Штраузера
Марко Сулла

27

Гаразд, я вдарюся цим. Python передає посилання на об'єкт, яке відрізняється від того, що ви зазвичай вважаєте "посиланням" або "значенням". Візьмемо цей приклад:

def foo(x):
    print x

bar = 'some value'
foo(bar)

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

Коли ти дзвониш foo(bar), ти barсам по собі не передаєш . Ви передаєте barзначення ': вказівник на' якесь значення '. На той момент є два "вказівники" на один і той же рядовий об'єкт.

А тепер порівняйте це з:

def foo(x):
    x = 'another value'
    print x

bar = 'some value'
foo(bar)

Ось у чому різниця. У рядку:

x = 'another value'

Ви фактично не змінюєте вміст x. Насправді це навіть неможливо. Натомість ви створюєте новий об’єкт рядка зі значенням „інше значення”. Це оператор присвоєння? Це не означає "перезаписати те, на що xвказує нове значення". Це говорить "оновити, xщоб натомість вказати на новий об'єкт". Після цього рядка є два рядкові об'єкти: "деяке значення" (з barвказівкою на нього) і "інше значення" (з xвказівкою на нього).

Це не незграбно. Коли ви розумієте, як це працює, це прекрасно елегантна, ефективна система.


3
Але що, якщо я хочу, щоб функція змінила вміст моєї змінної? Я вважаю, що немає можливості зробити це?
aquirdturtle

1
Ви можете змінити об'єкт, на який вказує ім'я , наприклад, додавши до списку, якщо цей об'єкт можна змінювати . Немає ніякої семантики Python для висловлювання "змінити простір імен області, яка мене викликала, так що ім'я у моєму списку аргументів тепер вказує на щось інше, ніж коли воно викликало мене".
Кірк Штраузер

2
Насправді я запитую, чи є простий спосіб отримати python не створювати новий вказівник на той самий об’єкт, а використовувати замість нього оригінальний вказівник. Я вважаю, що немає можливості зробити це. Я беру з інших відповідей, що звичайним обхідним шляхом для досягнення того, що робить передача за посиланням, є просто повернення більшої кількості параметрів. якщо це так, я бачу певну обгрунтованість аргументу, що a, b, c, d, e = foo (a, b, c, d, e) трохи незграбніший, ніж просто foo (a, b, c, г, д).
aquirdturtle

2
@aquirdturtle Це нічого не робить той факт, що створюється новий вказівник. Вам потрібно повернути новий об’єкт, оскільки strings, tuples та інші об’єкти незмінні . Якщо ви передаєте змінний об'єкт, такий як a list, ви можете змінити його всередині функції, не потребуючи повернення, оскільки всі параметри функції є вказівниками на ті самі об'єкти, які ви передали.
Марко Сулла

24

Сподіваюся, наступний опис це добре підсумовує:

Тут слід враховувати дві речі - змінні та об’єкти.

  1. Якщо ви передаєте змінну, тоді вона передається значенням, що означає, що зміни, внесені до змінної в межах функції, є локальними для цієї функції і, отже, не відображатимуться глобально. Це скоріше поведінка, схожа на "С".

Приклад:

def changeval( myvar ):
   myvar = 20; 
   print "values inside the function: ", myvar
   return

myvar = 10;
changeval( myvar );
print "values outside the function: ", myvar

O / P:

values inside the function:  20 
values outside the function:  10
  1. Якщо ви передаєте змінні, упаковані всередину змінного об'єкта, як список, тоді зміни, внесені до об'єкта, відображаються глобально, доки об'єкт не буде призначений повторно.

Приклад:

def changelist( mylist ):
   mylist2=['a'];
   mylist.append(mylist2);
   print "values inside the function: ", mylist
   return

mylist = [1,2,3];
changelist( mylist );
print "values outside the function: ", mylist

O / P:

values inside the function:  [1, 2, 3, ['a']]
values outside the function:  [1, 2, 3, ['a']]
  1. Тепер розглянемо випадок, коли об’єкт перепризначається. У цьому випадку об'єкт посилається на нове розташування пам'яті, яке є локальним для функції, в якій це відбувається, і, отже, не відображається глобально.

Приклад:

def changelist( mylist ):
   mylist=['a'];
   print "values inside the function: ", mylist
   return

mylist = [1,2,3];
changelist( mylist );
print "values outside the function: ", mylist

O / P:

values inside the function:  ['a']
values outside the function:  [1, 2, 3]

2
Це найкращий підсумок! ^^^ КРАЩЕ РЕЗЮМЕ ТУТ ^^^ всі прочитали цю відповідь! 1. передавати прості змінні 2. передавати посилання на об'єкт, яке модифікується 3. передавати посилання на об'єкт, яке перепризначається у функції
gaoithe

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

9

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

  1. Ось чому це не побічне значення. Оскільки

    def append(list):
        list.append(1)
    
    list = [0]
    reassign(list)
    append(list)
    

повертає [0,1], показуючи, що якесь посилання було явно передано як передане значення, не дозволяє функції взагалі змінювати батьківський обсяг .

Тоді схоже на передачу-посилання, га? Ні.

  1. Ось чому це не прохідне посилання. Оскільки

    def reassign(list):
      list = [0, 1]
    
    list = [0]
    reassign(list)
    print list
    

повертає [0], показуючи, що вихідне посилання було знищено під час перепризначення списку. прохідне посилання повернуло б [0,1] .

Для отримання додаткової інформації дивіться тут :

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

from copy import copy

def append(list):
  list2 = copy(list)
  list2.append(1)
  print list2

list = [0]
append(list)
print list

4

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

  • Незмінні аргументи ефективно передаються за значенням : рядок, ціле число, кортеж - це всі незмінні типи об’єктів. Хоча вони технічно "передаються за посиланням" (як і всі параметри), оскільки ви не можете змінити їх на місці всередині функції, вона виглядає / поводиться так, ніби передається за значенням.

  • Змінні аргументи ефективно передаються за допомогою посилання : списки або словники передаються його вказівниками. Будь-яка зміна на місці всередині функції, як-от (додати або розділити), вплине на оригінальний об'єкт.

Ось як розроблений Python: копії відсутні, і всі передаються за посиланням. Ви можете явно передати копію.

def sort(array):
    # do sort
    return array

data = [1, 2, 3]
sort(data[:]) # here you passed a copy

Останній момент, я хотів би згадати, яка функція має свою сферу дії.

def do_any_stuff_to_these_objects(a, b): 
    a = a * 2 
    del b['last_name']

number = 1 # immutable
hashmap = {'first_name' : 'john', 'last_name': 'legend'} # mutable
do_any_stuff_to_these_objects(number, hashmap) 
print(number) # 1 , oh  it should be 2 ! no a is changed inisde the function scope
print(hashmap) # {'first_name': 'john'}

1
"Незмінні аргументи ефективно передаються за значенням: рядок, ціле число, кортеж передаються за посиланням" у цьому реченні є суперечність
eral,

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

2

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

l = [0]

def set_3(x):
    x[0] = 3

set_3(l)
print(l[0])

У наведеному вище коді функція модифікує вміст об'єкта List (який можна змінювати), і, таким чином, на виході виходить 3 замість 0.

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


це відповідає на половину запитання, так як би я написав ту саму функцію set_3 (x), щоб вона не змінила значення l, а повернула новий список, який було змінено?
Тімоті Лоумен

Просто складіть новий список і змініть його:y = [a for a in x]; y[0] = 3; return y
Ісаак

вся справа полягає у списках та копіюванні списків, що певним чином відповідає, але як я можу це зробити, не перетворюючи int на список, чи це найкраще, що ми маємо?
Тімоті Лоумен

Ви не можете змінити примітивний аргумент, як int. Ви повинні покласти його в якусь тару. Мій приклад помістив це у список. Ви також можете помістити його в клас або словник. Насправді, вам слід просто перепризначити його поза функцією чимось на зразок x = foo(x).
Ісаак

0

Відповідь дана

def set_4(x):
   y = []
   for i in x:
       y.append(i)
   y[0] = 4
   return y

і

l = [0]

def set_3(x):
     x[0] = 3

set_3(l)
print(l[0])

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

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


0

Враховуйте, що змінна - це поле, і значення, на яке вона вказує, є "річчю" всередині поля:

1. Передайте посилання: функція поділяє одне і те ж поле, а отже, і річ всередині.

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

3. Передайте посилання на об'єкт : функція створює поле, але воно охоплює те саме, що було закрито початковим полем. Так і в Python :

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

б) якщо річ незмінна (наприклад, рядки python та числові типи), то у вікні всередині функції буде те саме те, ЩО ПОКИ ви не спробуєте змінити її значення. Після зміни річ у вікні функції є абсолютно новою річчю порівняно з початковою. Отже, id () для цього вікна тепер дасть ідентичність нової речі, яку він включає .


0
class demoClass:
    x = 4
    y = 3
foo1 = demoClass()
foo1.x = 2
foo2 = demoClass()
foo2.y = 5
def mySquare(myObj):
    myObj.x = myObj.x**2
    myObj.y = myObj.y**2
print('foo1.x =', foo1.x)
print('foo1.y =', foo1.y)
print('foo2.x =', foo2.x)
print('foo2.y =', foo2.y)
mySquare(foo1)
mySquare(foo2)
print('After square:')
print('foo1.x =', foo1.x)
print('foo1.y =', foo1.y)
print('foo2.x =', foo2.x)
print('foo2.y =', foo2.y)

-2

У Python передача за посиланням або за значенням пов'язана з тим, які фактичні об'єкти ви передаєте. Отже, якщо ви передаєте список, наприклад, ви фактично робите це передавання за посиланням, оскільки список є змінним об'єктом. Таким чином, ви передаєте вказівник на функцію, і ви можете змінити об'єкт (список) у тілі функції.

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


1
Це абсолютно неправильно. Python завжди передає "посилання на об'єкт" (яке відрізняється від посилання на змінну).
Кірк Штраузер

я мав на увазі аналогічні змінні об'єкти - передавати повз посилання та незмінні об'єкти - передавати значення. Це цілком дійсне те, що я кажу. Ви, мабуть, не зрозуміли того, що я говорив
jkalivas

1
Я зловив, і це неправильно. Це не має нічого спільного з тим, чи є об’єкт змінним чи незмінним; це завжди робиться однаково.
Кірк Штраузер,

-2

Python вже викликає посилання ..

візьмемо приклад:

  def foo(var):
      print(hex(id(var)))


  x = 1 # any value
  print(hex(id(x))) # I think the id() give the ref... 
  foo(x)

Вихід

  0x50d43700 #with you might give another hex number deppend on your memory 
  0x50d43700

Ні, це неправдаdef foo(var): print(id(var)) var = 1234 print(id(var)) x = 123 print(id(x)) # I think the id() give the ref... foo(x) print(id(x))
Zohaib Ijaz
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.