Сегодня буду краток. Есть код:
a, b, c = 1, 2, 3
arr = [a, b, c]
for i in range(3):
arr[i] += 1
print(a, b, c) # output: 1 2 3
А мне нужно output: 2 3 4. Как можно, изменяя элементы в списке, изменить элементы,
которые были добавлены изначально? Что-то вроде ссылки и указателя?
UPD: я значительно упростил код, поэтому дам некоторые уточнения: так называемые
переменные a, b и c являются атрибутами класса, и я сомневаюсь, что можно решить проблему
через globals()
Ответы
Ответ 1
Выражение arr[i] += 1 соответствует: arr[i] = arr[i] + 1 что переписывает ссылку,
на новую (с новым значением), поэтому ваш код и не работает.
Вообще a, b, c = 1, 2, 3 означает, что переменные a, b, c хранят ссылки на объекты
1, 2, 3. А тут arr = [a, b, c] вы не ссылки на переменные положили, а сами ссылки на
объекты 1, 2, 3.
Вам проще к самим переменным обратиться и поменять значением, чем так. Либо оставить
значения в словаре или в списке и работать с ними.
Но если очень хочется, можно рефлексией поменять у переменных:
a, b, c = 1, 2, 3
arr = [a, b, c]
for i in ['a', 'b', 'c']:
globals()[i] += 1
print(a, b, c) # 2 3 4
и я сомневаюсь, что можно решить проблему через globals()
Через globals() и вправду не получится, т.к. оно меняет глобальные переменные, а
вот для изменения в объектах использовать getattr и setattr:
class Foo:
a, b, c = 1, 2, 3
foo = Foo()
arr = [foo.a, foo.b, foo.c]
for i in ['a', 'b', 'c']:
x = getattr(foo, i)
setattr(foo, i, x + 1)
print(foo.a, foo.b, foo.c) # 2 3 4
Ответ 2
Kогда мне нужно обеспечить изменение объекта "по ссылке", я применяю такой способ:
a, b, c = [1], [2], [3]
arr = [a, b, c]
for i in range(3):
arr[i][0] += 1
print(a[0], b[0], c[0]) # output: 2 3 4
Объяснение.
Немного общей теории:
Объекты числового типа неизменяемы, к такому объекту нельзя прибавить единицу, можно
только создать новый числовой объект с нужным значением и заменить на него исходный.
Соответственно:
а = 1 означает: создай объект со значением 1 и помести его в глобальный словарь с
переменными (globals) под именем a.
а = a + 1 означает: найди в словаре с переменными объект под именем a, сложи его
с объектом 1, результат запиши в новый объект 2. В глобальном словаре замени значение
a ссылкой на этот объект.
a, b, c = 1, 2, 3 - создай три числовых объекта, запиши их ссылки в словарь с переменными
под именами a, b, c, соответственно. Тут ещё задействованы кортежи, но про них не буду
говорить, чтобы не усложнять.
Объекты типа list изменяемы, в список можно добавлять и удалять значения, можно менять
уже находящиеся в списке. Список хранит не сами объекты, а ссылки на них, меняя значение
в списке, мы на самом деле заменяем ссылку предыдущего объекта на новую, указывающую
на другой объект.
arr = [a, b, c] - создай объект типа список и наполни его ссылками на объекты, которые
хранятся в словаре с переменными под именами a, b, c. Какая ссылка хранится под именем
a? Ссылка на числовой объект 1. Под именем b ссылка на 2 и т. д. Таким образом, имеем
arr = [1, 2, 3] - список arr не хранит ни самих переменных, ни информации из каких
переменных объекты 1, 2, 3 были получены. Кстати, теперь каждый из трёх объектов 1,
2, 3 имеет на себя как минимум по две ссылки - одна в глобальном словаре с переменными
(a, b, c), другая внутри списка arr.
Когда вы делаете arr[0] += 1, вы берёте из списка нулевой элемент, который представляет
из себя ссылку на числовой объект 1 и прибавляете к этому объекту единицу. Информации
про переменную, с помощью которой вы этот объект назначали, в списке нет. Результат
(ссылку на новый объект) записываете обратно в нулевой элемент списка.
Использование списка для сохраненения ссылки на значение переменной:
a, b, c = [1], [2], [3] - оборачиваем каждый числовой объект в список и пишем ссылку
на этот список в переменную. Помним, что объект типа список хранит не сам числовой
объект, а ссылку на него. Следовательно, под именем a у нас хранится ссылка на список,
который содержит единственный элемент - ссылку на объект 1.
arr = [a, b, c] - эту строчку можно представить как arr = [[1], [2], [3]]. Информацию
о переменных a, b, c мы потеряли также, как и в предыдущем случае, однако сейчас и
переменная a, и arr[0] указывают на список [1]. И этот список содержит нужное нам значение,
которое мы можем менять, сохраняя в a и arr[0] ссылку на список-обёртку.
arr[0][0] += 1 - возьми нулевой элемент из списка. В нашем случае это [1]. Возьми
и у него нулевой элемент. Это будет числовой объект 1. Прибавь к нему единицу. Получившимся
новым объектом замени нулевой элемент в списке [1]. Список мы не трогаем, а лишь меняем
внутри него элемент на другой. Ссылка на список остаётся неизменной, переменная a продолжает
на него указывать, только теперь этот список содержит 2 вместо 1.
Мой ответ на похожую тему.
В мануале на английском этот способ под № 3: How do I write a function with output
parameters (call by reference)?.
Ответ 3
a, b, c = 1, 2, 3
arr = [a, b, c]
a, b, c = map(lambda x: x+1, arr)
print(a, b, c)
2 3 4