Как передать переменную по ссылке? - PullRequest
2341 голосов
/ 12 июня 2009

В документации Python неясно, передаются ли параметры по ссылке или по значению, и следующий код выдает неизменное значение 'Original'

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

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

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

Ответы [ 26 ]

3 голосов
/ 20 августа 2016

Хотя передача по ссылке - это не то, что хорошо вписывается в 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 советует, что изменение словаря может не сработать, но мне кажется, что оно работает.

2 голосов
/ 22 января 2019

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

  • Java & C #: типы примитивов (включая строку) передаются по значению (копия), тип ссылки передается по ссылке (копия адреса), поэтому все изменения, внесенные в параметр в вызываемой функции, видны вызывающий абонент.
  • C ++: Допускается как передача по ссылке, так и передача по значению. Если параметр передается по ссылке, вы можете изменить его или нет, в зависимости от того, был ли параметр передан как const или нет. Однако, const или нет, параметр поддерживает ссылку на объект, и ссылка не может быть назначена для указания на другой объект в вызываемой функции.
  • Python: Python - это «ссылка на объект», о которой часто говорят: «Ссылки на объекты передаются по значению». [Читать здесь] 1 . И вызывающая сторона, и функция ссылаются на один и тот же объект, но параметр в функции - это новая переменная, которая просто хранит копию объекта в вызывающей стороне. Как и в C ++, параметр может быть изменен или не изменен в функции - это зависит от типа передаваемого объекта. например; Тип неизменяемого объекта не может быть изменен в вызываемой функции, тогда как изменяемый объект может быть либо обновлен, либо повторно инициализирован. Принципиальное различие между обновлением или повторным назначением / повторной инициализацией изменяемой переменной заключается в том, что обновленное значение отражается обратно в вызываемой функции, а реинициализированное значение - нет. Область любого назначения нового объекта изменяемой переменной является локальной для функции в питоне. Примеры, предоставленные @ blair-conrad, прекрасно это понимают.
2 голосов
/ 10 сентября 2017

Поскольку ваш пример оказывается объектно-ориентированным, вы можете внести следующие изменения, чтобы получить аналогичный результат:

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'
0 голосов
/ 10 мая 2019

Поскольку, кажется, нигде не упоминается подход к моделированию ссылок, известный, например, из 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

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

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

0 голосов
/ 05 мая 2019

Поскольку словари передаются по ссылке, вы можете использовать переменную 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
0 голосов
/ 03 мая 2018

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

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