Вызов функций 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, что параметр является либо значением, либо параметром ссылки вместо использования global?

Во-вторых, на кембриджских экзаменах уровня A теперь говорится, что функция возвращает одно значение, а процедура возвращает более одного значения. В 80-х годах меня учили, что функция имеет оператор возврата, а процедура - нет. Почему сейчас это неверно?

Тимоти Лоуман
источник
5
Лучший ресурс, который я нашел для понимания модели вызовов Python, - это статья о effbot: effbot.org/zone/call-by-object.htm
Wilduck
1
вы должны прочитать о переменных Python, изменяемых и неизменяемых объектах. Также в вашем первом примере, почему должно nбыть изменено, вы используете новую переменную squareдля хранения результата вашего расчета.
Факундо Каско
6
Пожалуйста, не размещайте два несвязанных вопроса в одном. Определение функции и процедуры - это вопрос определения, и кажется, что ваше определение смещено в сторону Паскаля. На жаргоне Python нет такой вещи, как процедура.
Fred Foo
2
Также посмотрите этот предыдущий ответ на StackOverflow. stackoverflow.com/a/10262945/173292 Он довольно интуитивно объясняет вызов по объектной эталонной модели.
Wilduck
1
См. Также этот вопрос SO для очень полезного объяснения: как передать переменную по ссылке
Майкл Олрогге

Ответы:

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

Когда вы возвращаете такие значения, они возвращаются как кортеж, который, в свою очередь, распаковывается.

edit: Другой способ подумать об этом заключается в том, что, хотя вы не можете явно передавать переменные по ссылке в 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и что-то другое.
Дэйв Манкофф 08
спасибо, Дэйв! Я пытался «создать новый массив» в 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
Alex Punnen
В отличие от Java, в Python нет примитивного типа. Все типы экземпляров объектов.
Марко Сулла
184

По сути, существует три вида «вызовов функций»:

  • Передавать по значению
  • Пройти по ссылке
  • Переход по ссылке на объект

Python - это язык программирования ПРОПУСКАЕМЫХ ОБЪЕКТОВ.

Во-первых, важно понимать, что переменная и значение переменной (объекта) - это две разные вещи. Переменная «указывает» на объект. Переменная - это не объект. Очередной раз:

ПЕРЕМЕННАЯ НЕ ОБЪЕКТ

Пример: в следующей строке кода:

>>> x = []

[]- пустой список, x- это переменная, которая указывает на пустой список, но xсама по себе не является пустым списком.

Рассмотрим переменную ( xв приведенном выше случае) как поле, а «значение» переменной ( []) как объект внутри поля.

ПРОХОДИТЬ ПО ОБЪЕКТУ (Случай в Python):

Здесь «Ссылки на объекты передаются по значению».

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
Я до сих пор не понимаю, чем это отличается от передачи по ссылке. Этот ответ отчаянно нуждается в примере того, как идентичный псевдокод будет давать разные результаты для всех трех парадигм. Ссылка на другой ответ, который объясняет только разницу между передачей по ссылке и передачей по значению, не помогает.
Джесс Ридель
Не проще ли сказать, что «все переменные в 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. Если вы передаете переменную, она передается по значению, что означает, что изменения, внесенные в переменную внутри функции, являются локальными для этой функции и, следовательно, не будут отражаться глобально. Это больше похоже на поведение «C».

Пример:

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]
молодец182
источник
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 есть два типа объектов: неизменяемые и изменяемые, вот что происходит:

  • Неизменяемые аргументы эффективно передаются по значению : строка, целое число, кортеж - все это неизменяемые типы объектов. Хотя технически они «передаются по ссылке» (как и все параметры), поскольку вы не можете изменить их на месте внутри функции, они выглядят / ведут себя так, как будто они передаются по значению.

  • Изменяемые аргументы эффективно передаются по ссылке : списки или словари передаются по указателям. Любое изменение на месте внутри функции, например (append или del), повлияет на исходный объект.

Так устроен 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 05
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 или Pascal это кажется очень неуклюжим. Является ли это лучшим из имеющихся у нас методов?

Это не только неуклюже, но и включает в себя изменение исходного параметра каким-либо образом вручную, например, путем изменения исходного параметра в список: или копирования его в другой список вместо того, чтобы просто сказать: «используйте этот параметр как значение» или «используйте этот. в качестве справки ". Может ли быть простой ответ: для этого нет зарезервированного слова, но это отличная работа?

Тимоти Лоуман
источник
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 передача по ссылке или по значению связана с фактическими объектами, которые вы передаете. Таким образом, если вы передаете, например, список, вы фактически выполняете этот проход по ссылке, поскольку список является изменяемым объектом. Таким образом, вы передаете указатель на функцию и можете изменить объект (список) в теле функции.

Когда вы передаете строку, эта передача осуществляется по значению, поэтому создается новый строковый объект, и когда функция завершается, он уничтожается. Так что все связано с изменяемыми и неизменяемыми объектами.

Jkalivas
источник
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))
Зохайб Иджаз