Поведение глобальных переменных

Рейтинг: 8Ответов: 3Опубликовано: 12.05.2023

Данный код не изменит глобальные значения переменных x и y:

def abc(x, y):
    x = x + [4]
    y += 'nnn'
    return print(x, y)

x = [1, 2, 3]
y = 'abc'

abc(x, y)

print(x, y)

вывод программы:

[1, 2, 3, 4] abcnnn
[1, 2, 3] abc

Но если изменить оператор в выражении x = x + [4] на x += [4], то список изменится для глобального имени x. Вывод будет:

[1, 2, 3, 4] abcnnn
[1, 2, 3, 4] abc

Почему так происходит? То есть неизменяемая последовательность не изменилась, а изменяемый список все же изменился без использования слова global в области функции. Я понимаю, что такие вещи как x.append() поменяли бы глобальный x, но речь вроде идет о присваивании. А точнее о виде присваивания, что такого в операторе +=, что он позволяет менять глобальный x ?

Ответы

▲ 8Принят
def abc(x, y):
    x = x + [4]
    y += 'nnn'
    return print(x, y)

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

Когда вы делаете присваивание

x = x + [4]

- вы просто записываете в локальную переменную новое значение (новый список). Значение в глобальной переменной при этом никак не изменяется.

Когда вы делаете

x += [4]

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

И нет, x = x + ... не эквивалентно x += .... В первом случае создается новый список, результат записывается в x, во втором случае изменяется исходный список.

Если бы оператор + не создавал новый список, а модифицировал левый список, то вот такой код:

a = [1]
b = [2]
print(a + b)
print(a + b)
print(a + b)
print(a + b)

при каждом print выдавал бы новый результат (т.к. список a изменился), но он выдает один и тот же результат (как и ожидаешь интуитивно).

Хотя, конечно, ничего не мешает написать такой класс, в котором вызов __add__ модифицирует объект, а не создает новый, но это будет очень неинтуитивно.

▲ 4

+= - это не обязательно просто короткая запись для сложения с последующим присваиванием.

Вы можете для любого своего класса задать метод __iadd__, благодаря чему при += может быть выполнена вообще произвольная логика.

В частности для стандартного списка += работает как метод list.extend.

Поэтому он не пересоздаёт переменную с тем же именем, а изменяет существующую.

▲ 4
def abc(x):
    x += [4]
    print(f"2 x = {x} id(x) = {id(x)}")

if __name__ == '__main__':
    x = [1, 2, 3]
    print(f"1 x = {x} id(x) = {id(x)}")
    abc(x)
    print(f"3 x = {x} id(x) = {id(x)}")

Результат работы:

1 x = [1, 2, 3] id(x) = 2421664311424
2 x = [1, 2, 3, 4] id(x) = 2421664311424
3 x = [1, 2, 3, 4] id(x) = 2421664311424

Это один и тот же объект id = 2421664311424