Как передать переменную по ссылке?

2629

Документация Python кажется неясной относительно того, передаются ли параметры по ссылке или по значению, и следующий код выдает неизменное значение «Original»

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

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

Что я могу сделать, чтобы передать переменную по фактической ссылке?

Дэвид Сайкс
источник
23
Для краткого объяснения / разъяснения см. Первый ответ на этот вопрос stackoverflow . Поскольку строки являются неизменяемыми, они не будут изменены, и будет создана новая переменная, поэтому «внешняя» переменная все еще имеет то же значение.
PhilS
10
Код в ответе BlairConrad хорош, но объяснение, предоставленное DavidCournapeau и DarenThomas, является правильным.
Итан Фурман
70
Перед прочтением выбранного ответа, пожалуйста, прочитайте этот короткий текст. В других языках есть «переменные», в Python - «имена» . Подумайте об «именах» и «объектах» вместо «переменных» и «ссылок», и вам следует избегать множества подобных проблем.
2012 года
24
Почему это без необходимости использовать класс? Это не Java: P
Питер Р
2
Еще один обходной путь - создать «ссылку» обертки, например: ref = type ('', (), {'n': 1}) stackoverflow.com/a/1123054/409638
Роберт

Ответы:

2832

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

  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указано.

Надеюсь, это немного прояснит ситуацию.

РЕДАКТИРОВАТЬ: Было отмечено, что это не отвечает на вопрос, который первоначально задавал @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])

Хотя это кажется немного громоздким.

Блэр Конрад
источник
165
Затем то же самое происходит в C, когда вы передаете «по ссылке», вы фактически передаете по значению ссылку ... Определите «по ссылке»: P
Андреа Амбу
104
Я не уверен, что понимаю ваши условия. Я был вне игры C некоторое время, но когда я был в ней, не было никакого «прохода по ссылке» - вы могли передавать вещи, и это всегда передавалось по значению, поэтому все, что было в списке параметров был скопирован. Но иногда это был указатель, который мог следовать за частью памяти (примитив, массив, структура и т. Д.), Но вы не могли изменить указатель, который был скопирован из внешней области видимости - когда вы закончили с функцией исходный указатель все еще указывал на тот же адрес. C ++ ввел ссылки, которые вели себя по-разному.
Блэр Конрад
34
@ Zac Bowling Я не совсем понимаю, насколько то, что вы говорите, имеет практическое значение для этого ответа. Если новичок в Python хотел знать о передаче через ref / val, то ответ из этого ответа таков : 1- Вы можете использовать ссылку, которую функция получает в качестве аргументов, для изменения внешнего значения переменной, пока так как вы не переназначаете параметр для ссылки на новый объект. 2- Присвоение неизменяемому типу всегда создает новый объект, который разрывает ссылку, которую вы имели на внешнюю переменную.
Кэм Джексон
11
@CamJackson, вам нужен лучший пример - числа также являются неизменяемыми объектами в Python. Кроме того, не правда ли сказать, что любое присвоение без подписки в левой части равенства будет переназначать имя новому объекту, является ли оно неизменным или нет? неdef Foo(alist): alist = [1,2,3] будет изменять содержимое списка с точки зрения абонентов.
Марк Рэнсом
59
-1. Показанный код хорош, объяснение того, как совершенно неправильно. См. Ответы DavidCournapeau или DarenThomas для правильного объяснения причин.
Итан Фурман
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 - это ссылки, передаваемые по значению». Я думаю, что если вы понимаете только этот абзац, все остальное просто имеет смысл и вытекает из этого логическим выводом. Тогда вам просто нужно знать, когда вы создаете новый объект и когда модифицируете существующий.
Кэм Джексон
3
Но как вы можете переназначить ссылку? Я думал, что вы не можете изменить адрес 'var', но теперь ваша строка "Изменено" будет сохранена в адресе памяти "var". По вашему описанию кажется, что «Изменено» и «Оригинал» принадлежат разным местам в памяти, и вы просто переключаете «var» на другой адрес. Это верно?
Glassjawed
9
@Glassjawed, я думаю, ты понял. «Changed» и «Original» - это два разных строковых объекта с разными адресами памяти, и «var» меняет указатель на один на указатель на другой.
Марк Рэнсом
Использование функции id () помогает прояснить ситуацию, потому что она проясняет, когда Python создает новый объект (так что я думаю, в любом случае).
Тим Ричардсон
1
@MinhTran в самых простых терминах, ссылка - это то, что «ссылается» на объект. Физическое представление этого, скорее всего, является указателем, но это просто деталь реализации. Это действительно абстрактное понятие в глубине души.
Марк Рэнсом
329

Я нашел другие ответы довольно длинными и сложными, поэтому я создал эту простую диаграмму, чтобы объяснить, как Python обрабатывает переменные и параметры. введите описание изображения здесь

Zenadix
источник
2
прекрасный, позволяет легко заметить тонкий дифференциал, что есть промежуточное назначение, не очевидное для случайного зрителя. +1
user22866
9
Неважно, является ли A изменчивым или нет. Если вы назначите что-то другое для B, A не изменится . Если объект изменчив, вы можете изменить его, конечно. Но это не имеет ничего общего с присвоением имени ...
Мартейн Питерс
1
@ Martijn Ты прав. Я удалил часть ответа, в которой упоминается изменчивость. Я не думаю, что теперь все может стать проще.
Zenadix
4
Спасибо за обновление, намного лучше! Что смущает большинство людей, так это назначение на подписку; например B[0] = 2, против прямого назначения B = 2.
Мартин Питерс
11
«А назначен на Б.» Разве это не двусмысленно? Я думаю, что в обычном английском это может означать или 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 = [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 также есть примитивы, которые передаются путем копирования значения примитива. Таким образом, они отличаются в этом случае.
Клавдиу
175

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

Таким образом, при передаче списка в функцию / метод, список присваивается имени параметра. Добавление к списку приведет к изменению списка. Переназначение списка внутри функции не изменит первоначальный список, поскольку:

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
На первый взгляд этот ответ, кажется, обходит исходный вопрос. После второго чтения я понял, что это проясняет ситуацию. Хорошее продолжение этой концепции «присвоения имен» можно найти здесь: Код Как Pythonista: Идиоматический Python
Кристиан Груло
65

Эффбот (он же Фредрик Лунд) описал стиль передачи переменных в Python как call-by-object: 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]. Пустой список создан. И «х», и «у» указывают на один и тот же список. Метод append изменяет (обновляет) объект списка (например, добавляет запись в базу данных), и результат виден как "x", так и "y" (точно так же, как обновление базы данных будет видно для каждого соединения с этой базой данных).

Надеюсь, это прояснит проблему для вас.

Раймонд Хеттингер
источник
2
Я действительно ценю узнать об этом от разработчика. Правда ли, что id()функция возвращает значение указателя (ссылки на объект), как предполагает ответ pepr?
Честный Абэ
4
@HonestAbe Да, в CPython id () возвращает адрес. Но в других питонах, таких как PyPy и Jython, id () - это просто уникальный идентификатор объекта.
Рэймонд Хеттингер
59

Технически, Python всегда использует проход по ссылочным значениям . Я собираюсь повторить мой другой ответ, чтобы поддержать мое утверждение.

Python всегда использует значения передачи по ссылке. Нет никаких исключений. Любое присвоение переменной означает копирование контрольного значения. Не исключение Любая переменная - это имя, связанное с эталонным значением. Всегда.

Вы можете думать о ссылочном значении как адрес целевого объекта. Адрес автоматически разыменовывается при использовании. Таким образом, работая со ссылочным значением, вы, кажется, работаете непосредственно с целевым объектом. Но между ними всегда есть ссылка, еще один шаг, чтобы перейти к цели.

Вот пример, который доказывает, что Python использует передачу по ссылке:

Иллюстрированный пример передачи аргумента

Если аргумент был передан по значению, внешний lstне может быть изменен. Зеленый - это целевые объекты (черный - это значение, хранящееся внутри, красный - это тип объекта), желтый - это память с эталонным значением внутри - нарисованная как стрелка. Синяя сплошная стрелка - это контрольное значение, которое было передано функции (через пунктирную синюю стрелку). Уродливый темно-желтый - это внутренний словарь. (На самом деле его можно нарисовать также в виде зеленого эллипса. Цвет и форма говорят только о том, что он внутренний.)

Вы можете использовать id()встроенную функцию, чтобы узнать, что такое эталонное значение (то есть адрес целевого объекта).

В скомпилированных языках переменная - это область памяти, которая может захватывать значение типа. В Python переменная - это имя (захваченное внутри как строка), привязанное к ссылочной переменной, которая содержит ссылочное значение для целевого объекта. Имя переменной - это ключ во внутреннем словаре, часть значения этого элемента словаря хранит эталонное значение для цели.

Справочные значения скрыты в Python. Не существует явного пользовательского типа для хранения ссылочного значения. Тем не менее, вы можете использовать элемент списка (или элемент в любом другом подходящем типе контейнера) в качестве ссылочной переменной, потому что все контейнеры хранят элементы также как ссылки на целевые объекты. Другими словами, элементы на самом деле не содержатся внутри контейнера - есть только ссылки на элементы.

pepr
источник
1
Фактически это подтверждается его передачей по эталонному значению. +1 за этот ответ, хотя пример не был хорошим.
BugShotGG
30
Придумывание новой терминологии (например, «передача по ссылочному значению» или «вызов по объекту» не помогает). «Вызов (значение | ссылка | имя)» являются стандартными терминами. «ссылка» является стандартным термином. Передача ссылок по значению точно описывает поведение Python, Java и множества других языков, используя стандартную терминологию.
cayhorstmann
4
@cayhorstmann: Проблема в том, что переменная Python не имеет такой же терминологии, как в других языках. Таким образом, вызов по ссылке не подходит здесь. Кроме того, как вы точно определите термин ссылка ? Неформально путь Python можно легко описать как передачу адреса объекта. Но это не соответствует потенциально распределенной реализации Python.
Пепр
1
Мне нравится этот ответ, но вы можете подумать, действительно ли пример помогает или мешает процессу. Кроме того, если вы замените «ссылочное значение» на «объектную ссылку», вы будете использовать терминологию, которую мы могли бы считать «официальной», как показано здесь: Определение функций
Честный Абэ
2
В конце этой цитаты указана сноска, которая гласит: «На самом деле, вызов по ссылке на объект был бы лучшим описанием, поскольку, если передается изменяемый объект, вызывающая сторона увидит любые изменения, внесенные в него вызываемым ... « Я согласен с вами, что путаница вызвана попыткой согласовать терминологию, установленную с другими языками. Помимо семантики, вещи, которые необходимо понять, это: словари / пространства имен, операции связывания имен и отношения имя → указатель → объект (как вы уже знаете).
Честный Абэ
56

В Python нет переменных

Ключ к пониманию передачи параметров - перестать думать о «переменных». В Python есть имена и объекты, и вместе они выглядят как переменные, но полезно всегда различать три.

  1. У Python есть имена и объекты.
  2. Назначение связывает имя с объектом.
  3. Передача аргумента в функцию также связывает имя (имя параметра функции) с объектом.

Это все, что нужно сделать. Изменчивость не имеет отношения к этому вопросу.

Пример:

a = 1

Это связывает имя aс объектом типа integer, который содержит значение 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.variablepass [self.variable]и в функции modify var[0]).
Более питонический подход состоял бы в том, чтобы ввести тривиальный класс с одним атрибутом. Функция получает экземпляр класса и манипулирует атрибутом.

Лутц Пречелт
источник
20
«У Python нет переменных» - это глупый и запутанный лозунг, и мне бы очень хотелось, чтобы люди перестали это говорить ... :( Остальная часть этого ответа хороша
Нед Батчелдер,
9
Это может быть шокирующим, но это не глупо. И я не думаю, что это также сбивает с толку: мы надеемся, что это открывает разум получателя для грядущего объяснения и ставит ее в полезное отношение «интересно, что у них есть вместо переменных». (Да, ваш пробег может отличаться.)
Lutz Prechelt
13
Вы также сказали бы, что у Javascript нет переменных? Они работают так же, как Python. Кроме того, Java, Ruby, PHP, ... Я думаю, что лучшая методика обучения - «Переменные Python работают иначе, чем Си».
Нед Бэтчелдер
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 значения являются объектами, контейнеры - это объекты (видите проблему?), А имена на самом деле являются отдельными вещами. Я считаю, что гораздо сложнее получить точное понимание переменных таким образом. Объяснение имен и объектов кажется более сложным, но на самом деле проще.
Лутц Пречелт
43

Простой трюк, который я обычно использую, - это просто обернуть его в список:

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

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

(Да, я знаю, что это может быть неудобно, но иногда это достаточно просто сделать.)

AmanicA
источник
7
+1 для небольшого количества текста, дающего существенный обходной путь к проблеме Python, не имеющего переход по ссылке. (В качестве последующего комментария / вопроса, который подходит как здесь, так и в любом месте на этой странице: мне не понятно, почему python не может предоставить ключевое слово «ref», как в C #, которое просто оборачивает аргумент вызывающей стороны в список вроде это и обрабатывать ссылки на аргумент в функции как 0-й элемент списка.)
M Katz
5
Ницца. Чтобы пройти мимо ref, заверните в [].
Юстас
38

(изменить - Блэр обновил свой чрезвычайно популярный ответ, чтобы он стал точным)

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

Ответ Дэвида Курнапо указывает на реальный ответ и объясняет, почему поведение в посте Блэр Конрад кажется правильным, а определения - нет.

В той степени, в которой Python передается по значению, все языки передаются по значению, поскольку должен быть отправлен некоторый фрагмент данных (будь то «значение» или «ссылка»). Тем не менее, это не означает, что Python передается по значению в том смысле, что программист на С может подумать об этом.

Если вы хотите поведение, ответ Блэр Конрад в порядке. Но если вы хотите знать, почему Python не передается по значению или по ссылке, прочитайте ответ Дэвида Курнапо.

KobeJohn
источник
4
Это просто не правда, что все языки называют по значению. В C ++ или Pascal (и, конечно, во многих других, которых я не знаю), у вас есть вызов по ссылке. Например, в C ++ void swap(int& x, int& y) { int temp = x; x = y; y = temp; }поменяется местами переданные ему переменные. В Паскале вы используете varвместо &.
cayhorstmann
2
Я думал, что ответил на это давно, но я не вижу этого. Для полноты - cayhorstmann неправильно понял мой ответ. Я не говорил, что все называют по значению в терминах, которые большинство людей впервые узнают о 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' )
bobobobo
источник
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 передает массивы как указатели - изменяемые объекты могут быть изменены на месте в функции, так же, как массивы C.
ajknzhol
источник
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/… В ответ на @Tim, Stack Overflow - это не только сайт вопросов и ответов, это обширный репозиторий справочных знаний, который только укрепляется и становится более нюансированным. как активный вики- с большим вкладом.
Макс П Маги
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

Следовательно, присвоение глобальной переменной, которая не объявлена ​​глобальной, создает новый локальный объект и разрывает ссылку на исходный объект.

Joop
источник
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
@Veky: я знаю об этом. В любом случае, литерал списка преобразуется в объект списка. На самом деле, любой объект в Python может существовать без имени, и его можно использовать, даже если ему не дано никакого имени. И вы можете думать о них как об анонимных объектах. Представьте, что объекты являются элементами списков. Им не нужно имя. Вы можете получить к ним доступ посредством индексации или перебора списка. Во всяком случае, я настаиваю на том, что [0, 1] = [1, 2, 3]это просто плохой пример. В Python нет ничего подобного.
pepr
@pepr: я не обязательно имею в виду имена с определением Python, просто обычные имена. Конечно, alist[2]считается именем третьего элемента alist. Но я думаю, что неправильно понял, в чем заключалась ваша проблема. :-)
Veky
Argh. Мой английский явно намного хуже, чем мой Python. :-) Я попробую только еще раз. Я только что сказал, что вы должны дать объекту некоторые имена, чтобы поговорить о них. Под этими «именами» я не имел в виду «имена, определенные Python». Я знаю механизмы Python, не волнуйтесь.
Veky
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

Это безобразный хак, но это работает. ;-П

itmuckel
источник
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

Я использовал следующий метод, чтобы быстро преобразовать пару кодов Фортрана в 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
Брэд Портер
источник
Да, это решает «передачу по ссылке» и в моем случае использования. У меня есть функция, которая в основном очищает значения в, dictа затем возвращает dict. Однако во время очистки может стать очевидным, что требуется перестройка части системы. Следовательно, функция должна не только возвращать очищенные, dictно и иметь возможность сигнализировать о восстановлении. Я пытался передать boolпо ссылке, но OFC, который не работает. Выяснив, как решить эту проблему, я обнаружил, что ваше решение (в основном возвращающее кортеж) работает лучше всего, и при этом вообще не является взломом / обходным путем (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-х оболочек для доступа к переменной немного громоздка, поэтому их можно обернуть в класс с атрибутом proxy:

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

Поддержка «отражения» Pythons позволяет получить объект, который способен переназначить имя / переменную в заданной области, не определяя функции явно в этой области:

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класс оборачивает словарь доступа. Таким образом, атрибут access to wrappedпереводится в элемент доступа в переданном словаре. Передав результат встроенной функции localsи имя локальной переменной, вы получите доступ к локальной переменной. Документация Python от 3.5 советует, что изменение словаря может не сработать, но мне кажется, что это работает.

textshell
источник
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
Liakos
источник
3

Передача по ссылке в Python сильно отличается от концепции передачи по ссылке в C ++ / Java.

  • Java & C #: типы примитивов (включая строку) передаются по значению (копия), тип ссылки передается по ссылке (копирование адреса), поэтому все изменения, внесенные в параметр в вызываемой функции, видны вызывающей стороне.
  • C ++: допускается как передача по ссылке, так и передача по значению. Если параметр передается по ссылке, вы можете изменить его или нет, в зависимости от того, был ли параметр передан как const или нет. Однако, const или нет, параметр поддерживает ссылку на объект, и ссылка не может быть назначена для указания на другой объект в вызываемой функции.
  • Python: Python является «передачей по ссылке на объект», о которой часто говорят: «Ссылки на объекты передаются по значению». [Читать здесь] 1, И вызывающая сторона, и функция ссылаются на один и тот же объект, но параметр в функции - это новая переменная, которая просто хранит копию объекта в вызывающей стороне. Как и в C ++, параметр может быть изменен или не изменен в функции - это зависит от типа передаваемого объекта. например; Тип неизменяемого объекта не может быть изменен в вызываемой функции, тогда как изменяемый объект может быть либо обновлен, либо повторно инициализирован. Принципиальное различие между обновлением или повторным назначением / повторной инициализацией изменяемой переменной заключается в том, что обновленное значение отражается обратно в вызываемой функции, а реинициализированное значение - нет. Область любого назначения нового объекта изменяемой переменной является локальной для функции в питоне. Примеры, предоставленные @ blair-conrad, прекрасно это понимают.
Алок Гарг
источник
1
Старый, но я чувствую себя обязанным исправить это. Строки передаются по ссылке как в Java, так и в C #, а НЕ по значению
John
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
Хотя это работает. Это не передать по ссылке. Это «передача по ссылке на объект».
Бишвис Мишра
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)
sergzach
источник
1

Поскольку, кажется, нигде не упоминается, подход для имитации ссылок, известный, например, из C ++, состоит в том, чтобы использовать функцию «update» и передавать ее вместо фактической переменной (или, скорее, «name»):

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

Это в основном полезно для «внешних ссылок» или в ситуации с несколькими потоками / процессами (делая функцию обновления потока / многопроцессорной безопасным).

Очевидно, что вышеизложенное не позволяет прочитать значение, а только обновить его.

Даниэль Жур
источник