Как передать переменную по ссылке?
Документация Python кажется неясной относительно того, передаются ли параметры по ссылке или по значению, и следующий код выдает неизменное значение "Original"
class PassByReference:
def __init__(self):
self.variable = 'Original'
self.change(self.variable)
print(self.variable)
def change(self, var):
var = 'Changed'
Что я могу сделать, чтобы передать переменную по фактической ссылке?
42 ответа
Аргументы передаются по назначению. Обоснование этого двоякое:
- переданный параметр на самом деле является ссылкой на объект (но ссылка передается по значению)
- некоторые типы данных являются изменчивыми, а другие нет
Так:
Если вы передадите изменяемый объект в метод, метод получит ссылку на этот же объект, и вы сможете изменить его на радость своего сердца, но если перепривязать ссылку в методе, внешняя область ничего не будет знать об этом, и после все готово, внешняя ссылка все еще будет указывать на исходный объект.
Если вы передадите неизменный объект методу, вы все равно не сможете повторно привязать внешнюю ссылку и даже не можете изменить объект.
Чтобы было еще яснее, давайте приведем несколько примеров.
Список - изменяемый тип
Давайте попробуем изменить список, который был передан методу:
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])
Хотя это кажется немного громоздким.
Проблема возникает из-за неправильного понимания того, какие переменные есть в 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'
Это не передача по значению или передача по ссылке - это вызов по объекту. Посмотрите на это, Фредрик Лунд:
http://effbot.org/zone/call-by-object.htm
Вот важная цитата:
"... переменные [имена] не являются объектами; их нельзя обозначать другими переменными или ссылаться на объекты".
В вашем примере, когда Change
вызывается метод - для него создается пространство имен; а также var
становится именем в этом пространстве имен для строкового объекта 'Original'
, Этот объект затем имеет имя в двух пространствах имен. Следующий, var = 'Changed'
связывает var
к новому строковому объекту, и, таким образом, пространство имен метода забывает о 'Original'
, Наконец, это пространство имен забыто, и строка 'Changed'
вместе с этим.
Подумайте о вещах, передаваемых по присваиванию, а не по ссылке / по значению. Таким образом, всегда понятно, что происходит, пока вы понимаете, что происходит во время обычного задания.
Таким образом, при передаче списка в функцию / метод, список присваивается имени параметра. Добавление к списку приведет к изменению списка. Переназначение списка внутри функции не изменит первоначальный список, так как:
a = [1, 2, 3]
b = a
b.append(4)
b = ['a', 'b']
print a, b # prints [1, 2, 3, 4] ['a', 'b']
Поскольку неизменяемые типы не могут быть изменены, они, похоже, передаются по значению - передача int в функцию означает присвоение int параметру functions. Вы можете только переназначить это, но это не изменит значение исходных переменных.
В Python нет переменных
Ключ к пониманию передачи параметров - перестать думать о "переменных". В Python есть имена и объекты, и вместе они выглядят как переменные, но полезно всегда различать три.
- У Python есть имена и объекты.
- Назначение привязывает имя к объекту.
- Передача аргумента в функцию также связывает имя (имя параметра функции) с объектом.
Это все, что нужно сделать. Изменчивость не имеет значения для этого вопроса.
Пример:
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
) снова присваивает то же имя: другому объекту (который также содержит строку, но мог бы быть чем-то другим).
Эффбот (он же Фредрик Лунд) описал стиль передачи переменных в Python как call-by-object: http://effbot.org/zone/call-by-object.htm
Объекты размещаются в куче, а указатели на них можно передавать где угодно.
Когда вы делаете назначение, такое как
x = 1000
создается словарная запись, которая отображает строку "x" в текущем пространстве имен на указатель на целочисленный объект, содержащий тысячу.Когда вы обновляете "х" с
x = 2000
создается новый целочисленный объект и словарь обновляется, чтобы указывать на новый объект. Старый объект тысяча неизменен (и может или не может быть живым в зависимости от того, относится ли что-либо еще к объекту).Когда вы делаете новое назначение, такое как
y = x
создается новая словарная статья "y", которая указывает на тот же объект, что и запись для "x".Объекты, такие как строки и целые числа, являются неизменными. Это просто означает, что нет методов, которые могут изменить объект после его создания. Например, как только создается целочисленный объект тысяча, он никогда не изменится. Математика делается путем создания новых целочисленных объектов.
Такие объекты, как списки, являются изменчивыми. Это означает, что содержимое объекта может быть изменено любым указанием на объект. Например,
x = []; y = x; x.append(10); print y
распечатает[10]
, Пустой список создан. И "х", и "у" указывают на один и тот же список. Метод append изменяет (обновляет) объект списка (например, добавляет запись в базу данных), и результат виден как "x", так и "y" (точно так же, как обновление базы данных будет видно для каждого соединения с этой базой данных).
Надеюсь, что это проясняет проблему для вас.
Технически, Python всегда использует проход по ссылочным значениям. Я собираюсь повторить мой другой ответ, чтобы поддержать мое утверждение.
Python всегда использует значения передачи по ссылке. Нет никаких исключений. Любое присвоение переменной означает копирование контрольного значения. Не исключение Любая переменная - это имя, связанное с эталонным значением. Всегда.
Вы можете думать о ссылочном значении как адрес целевого объекта. Адрес автоматически разыменовывается при использовании. Таким образом, работая со ссылочным значением, кажется, что вы работаете непосредственно с целевым объектом. Но между ними всегда есть ссылка, еще один шаг, чтобы перейти к цели.
Вот пример, который доказывает, что Python использует передачу по ссылке:
Если аргумент был передан по значению, внешний lst
не может быть изменено. Зеленый - это целевые объекты (черный - это значение, хранящееся внутри, красный - это тип объекта), желтый - это память с эталонным значением внутри - нарисованная как стрелка. Синяя сплошная стрелка - это контрольное значение, которое было передано функции (через пунктирную синюю стрелку). Уродливый темно-желтый - это внутренний словарь. (На самом деле его можно нарисовать также в виде зеленого эллипса. Цвет и форма говорят только о том, что он внутренний.)
Вы можете использовать id()
встроенная функция, чтобы узнать, что такое эталонное значение (то есть адрес целевого объекта).
В скомпилированных языках переменная - это область памяти, которая может захватывать значение типа. В Python переменная - это имя (захваченное внутри как строка), привязанное к ссылочной переменной, которая содержит ссылочное значение для целевого объекта. Имя переменной - это ключ во внутреннем словаре, часть значения этого элемента словаря хранит эталонное значение для цели.
Справочные значения скрыты в Python. Не существует явного пользовательского типа для хранения ссылочного значения. Тем не менее, вы можете использовать элемент списка (или элемент в любом другом подходящем типе контейнера) в качестве ссылочной переменной, потому что все контейнеры хранят элементы также как ссылки на целевые объекты. Другими словами, элементы на самом деле не содержатся внутри контейнера - только ссылки на элементы.
Простой трюк, который я обычно использую, - это просто обернуть его в список:
def Change(self, var):
var[0] = 'Changed'
variable = ['Original']
self.Change(variable)
print variable[0]
(Да, я знаю, что это может быть неудобно, но иногда это достаточно просто сделать.)
(изменить - Блэр обновил свой чрезвычайно популярный ответ, чтобы он стал точным)
Я думаю, что важно отметить, что нынешний пост, набравший наибольшее количество голосов (Блэр Конрад), будучи верным в отношении своего результата, вводит в заблуждение и является неправильным на грани на основе его определений. Хотя существует много языков (например, C), которые позволяют пользователю передавать по ссылке или по значению, Python не является одним из них.
Ответ Дэвида Курнапо указывает на реальный ответ и объясняет, почему поведение в посте Блэр Конрад кажется правильным, а определения - нет.
В той степени, в которой Python передается по значению, все языки передаются по значению, поскольку должен быть отправлен некоторый фрагмент данных (будь то "значение" или "ссылка"). Тем не менее, это не означает, что Python передается по значению в том смысле, что программист на С может подумать об этом.
Если вы хотите поведение, ответ Блэр Конрад в порядке. Но если вы хотите узнать, почему Python не является ни по значению, ни по ссылке, прочитайте ответ Дэвида Курнапо.
У вас есть действительно хорошие ответы здесь.
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' )
Схема передачи по присваиванию в Python не совсем совпадает с опцией параметров ссылок C++, но на практике она очень похожа на модель передачи аргументов языка C (и других):
- Неизменяемые аргументы эффективно передаются "по значению". Такие объекты, как целые числа и строки, передаются по ссылке на объект, а не путем копирования, но поскольку вы не можете изменить неизменяемые объекты на месте, эффект во многом напоминает создание копии.
- Изменяемые аргументы эффективно передаются "по указателю". Такие объекты, как списки и словари, также передаются по ссылкам на объекты, что аналогично тому, как C передает массивы как указатели - изменяемые объекты могут быть изменены на месте в функции, так же, как массивы C,
В этом случае переменная под названием 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']
>>>
Я уверен, что кто-то еще мог бы уточнить это дальше.
Здесь есть много идей для ответов, но я думаю, что дополнительный момент здесь явно не упоминается. Цитирование из документации по Python https://docs.python.org/2/faq/programming.html
"В 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
Следовательно, присвоение глобальной переменной, которая не объявлена глобальной, создает новый локальный объект и разрывает ссылку на исходный объект.
Как вы можете утверждать, у вас должен быть изменяемый объект, но позвольте мне предложить вам проверить глобальные переменные, так как они могут помочь вам или даже решить эту проблему!
http://docs.python.org/3/faq/programming.html
пример:
>>> 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
Вот простое (я надеюсь) объяснение концепции 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)
что, очевидно, изменит объект. Этот ответ объясняет это хорошо.
Помимо всех замечательных объяснений того, как все это работает в Python, я не вижу простого предложения по проблеме. Как вы, кажется, создаете объекты и экземпляры, питонический способ обработки переменных экземпляра и их изменения заключается в следующем:
class PassByReference:
def __init__(self):
self.variable = 'Original'
self.Change()
print self.variable
def Change(self):
self.variable = 'Changed'
В методах экземпляра вы обычно ссылаетесь на self
для доступа к атрибутам экземпляра. Это нормально, чтобы установить атрибуты экземпляра в __init__
и читать или изменять их в методах экземпляра. Это также, почему вы проходите self
Als первый аргумент 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
Я использовал следующий метод, чтобы быстро преобразовать пару кодов Фортрана в 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
Есть небольшая хитрость, чтобы передать объект по ссылке, хотя язык не позволяет это сделать. Он работает и в 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
Это безобразный хак, но это работает.;-П
Поскольку, кажется, нигде не упоминается, подход к моделированию ссылок, известный, например, из 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
Это в основном полезно для "внешних ссылок" или в ситуации с несколькими потоками / процессами (делая функцию обновления thread / multiprocessing безопасной).
Obviously the above does not allow reading the value, only updating it.
Поскольку словари передаются по ссылке, вы можете использовать переменную 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
Учитывая способ, которым python обрабатывает значения и ссылки на них, единственный способ ссылаться на произвольный атрибут экземпляра - по имени:
class PassByReferenceIsh:
def __init__(self):
self.variable = 'Original'
self.change('variable')
print self.variable
def change(self, var):
self.__dict__[var] = 'Changed'
в реальном коде вы, конечно, добавили бы проверку ошибок при поиске dict.
Поскольку ваш пример оказывается объектно-ориентированным, вы можете внести следующие изменения, чтобы получить аналогичный результат:
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'
Передача по ссылке в Python сильно отличается от концепции передачи по ссылке в C++ / Java.
- Java: все передается по ссылке, поэтому все изменения, внесенные в параметр в вызываемой функции, видны вызывающей стороне.
- C++: допускается как передача по ссылке, так и передача по значению. Если параметр передается по ссылке, вы можете изменить его или нет, в зависимости от того, был ли параметр передан как const или нет. Однако, const или нет, параметр поддерживает ссылку на объект, и ссылка не может быть назначена для указания на другой объект в вызываемой функции.
- Python: Python - это "ссылка на объект", о которой часто говорят: "Ссылки на объекты передаются по значению". [Читать здесь] 1. И вызывающая сторона, и функция ссылаются на один и тот же объект, но параметр в функции - это новая переменная, которая просто хранит копию объекта в вызывающей стороне. Как и в C++, параметр может быть изменен или не изменен в функции - это зависит от типа передаваемого объекта. например; Тип неизменяемого объекта не может быть изменен в вызываемой функции, тогда как изменяемый объект может быть либо обновлен, либо повторно инициализирован. Принципиальное различие между обновлением или повторным назначением / повторной инициализацией изменяемой переменной заключается в том, что обновленное значение отражается обратно в вызываемой функции, а реинициализированное значение - нет. Область любого назначения нового объекта изменяемой переменной является локальной для функции в питоне. Примеры, предоставленные @blair-conrad, прекрасно это понимают.
Хотя передача по ссылке - это не то, что хорошо вписывается в 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
Класс оборачивает словарь доступа. Так что атрибут доступа wrapped
переводится на предмет доступа в пропущенном словаре. Передав результат встроенного locals
и имя локальной переменной это приводит к получению доступа к локальной переменной. Документация Python от 3.5 советует, что изменение словаря может не сработать, но мне кажется, что это работает.
Вы можете просто использовать пустой класс в качестве экземпляра для хранения ссылочных объектов, поскольку внутренние атрибуты объекта хранятся в словаре экземпляра. Смотрите пример.
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)
Другие ответы показались мне немного запутанными, и мне пришлось некоторое время бороться, чтобы понять концепцию. Итак, я пытаюсь дать ответ на своем языке. Это может помочь вам, если другие ответы вас тоже сбивают с толку. Итак, ответ такой:
Когда вы создаете список-
my_list = []
вы фактически создаете объект списка классов:
my_list = list()
Здесь my_list — это просто имя, присвоенное адресу памяти (например, 140707924412080) объекта, созданного конструктором класса list.
Когда вы передаете этот список методу, определенному как
def my_method1(local_list):
local_list.append(1)
создается еще одна ссылка на тот же адрес памяти 140707924412080. Итак, когда вы вносите какие-либо изменения/мутируете объект с помощью метода добавления, это также отражается за пределами my_method1. Потому что внешний список my_list и local_list ссылаются на один и тот же адрес памяти.
С другой стороны, когда вы передаете тот же список следующему методу,
def my_method2(local_list2):
local_list2 = [1,2,3,4]
первая половина процесса остается прежней. т.е. создается новая ссылка/имя local_list2, которая указывает на тот же адрес памяти 140707924412080. Но когда вы создаете новый список [1,2,3,4], конструктор класса list вызывается снова и создается новый объект создан. Этот новый объект имеет совершенно другой адрес памяти, например, 140707924412112. Когда вы присваиваете local_list2 значению [1,2,3,4], теперь имя local_list2 относится к новому адресу памяти, который равен 140707924412112. Поскольку во всем этом процессе у вас есть не вносил никаких изменений в объект, размещенный по адресу памяти 140707924412080, он остается неизменным.
Другими словами, это соответствует тому, что «в других языках есть переменные, а в Python есть имена». Это означает, что в других языках переменные ссылаются на фиксированный адрес в памяти. Это означает, что в C++, если вы переназначаете переменную с помощью
a = 1
a = 2
адрес памяти, где хранилось значение «1», теперь содержит значение «2». И, следовательно, значение «1» полностью потеряно. В то время как в Python, поскольку все является объектом, ранее «a» относилось к адресу памяти, в котором хранится объект класса «int», который, в свою очередь, хранит значение «1». Но после переназначения он ссылается на совершенно другой адрес памяти, в котором хранится вновь созданный объект класса «int», содержащий значение «2».
Надеюсь, поможет.
в качестве альтернативы вы можете использовать ctypes, ведьма будет выглядеть примерно так
import ctypes
def f(a):
a.value=2398 ## resign the value in a function
a = ctypes.c_int(0)
print("pre f", a)
f(a)
print("post f", a)
as a - это ac int, а не целое число Python и передается по ссылке. однако вы должны быть осторожны, так как могут произойти странные вещи, и поэтому не рекомендуется
Я новичок в Python, начал вчера (хотя я программирую 45 лет).
Я пришел сюда, потому что я писал функцию, в которой я хотел иметь два так называемых выходных параметра. Если бы это был только один выходной параметр, я бы сейчас не зацикливался на проверке работы ссылки / значения в Python. Я бы просто использовал вместо этого возвращаемое значение функции. Но поскольку мне нужно было два таких выходных параметра, я почувствовал, что мне нужно разобраться с этим.
В этом посте я покажу, как я решил свою ситуацию. Возможно, пришедшие сюда другие сочтут это полезным, хотя это не совсем ответ на вопрос темы. Опытные программисты Python, конечно, уже знают о решении, которое я использовал, но для меня оно было новым.
Из приведенных здесь ответов я мог быстро увидеть, что Python в этом отношении немного похож на Javascript, и что вам нужно использовать обходные пути, если вам нужна справочная функция.
Но затем я обнаружил в Python кое-что изящное, чего я не видел раньше в других языках, а именно то, что вы можете вернуть более одного значения из функции простым способом, разделенным запятой, например:
def somefunction(p):
a=p+1
b=p+2
c=-p
return a, b, c
и что вы можете обрабатывать это на вызывающей стороне аналогичным образом, например
x, y, z = somefunction(w)
Для меня этого было достаточно, и я остался доволен. Не нужно использовать какое-то обходное решение.
На других языках вы, конечно, также можете возвращать много значений, но обычно в исходном состоянии объекта, и вам необходимо соответствующим образом настроить вызывающую сторону.
Способ Python сделать это был красивым и простым.
Если вы хотите еще больше имитировать по ссылке, вы можете сделать следующее:
def somefunction(a, b, c):
a = a * 2
b = b + a
c = a * b * c
return a, b, c
x = 3
y = 5
z = 10
print(F"Before : {x}, {y}, {z}")
x, y, z = somefunction(x, y, z)
print(F"After : {x}, {y}, {z}")
что дает этот результат
Раньше: 3, 5, 10 После: 6, 11, 660
Уже есть много отличных ответов (или, скажем, мнений) по этому поводу, и я их читал, но я хочу упомянуть пропущенный. Один из документации Python в разделе часто задаваемых вопросов. Я не знаю дату публикации этой страницы, но это должно быть нашим истинным ориентиром:
Помните, что в Python аргументы передаются путем присваивания . Поскольку присваивание просто создает ссылки на объекты, между именами аргументов в вызывающем и вызываемом объектах нет псевдонимов, и, следовательно, вызов по ссылке как таковой отсутствует.
Если у вас есть:
a = SOMETHING
def fn(arg):
pass
и вы называете это какfn(a)
, вы делаете именно то, что делаете в задании. Итак, это происходит:
arg = a
Дополнительная ссылка наSOMETHING
создано. Переменные — это просто символы/имена/ссылки. Они ничего не "держат".