Ссылки в Python или странное поведение некоторых конструкций

Написать данную статью меня подтолкнул пост на хабре. В Python есть конструкции, которые ведут себя не так как хотелось бы. Основная причина - ссылки, которые очень похожи на указатели в языках C/C++. Давайте взглянем на первый пример:

tmp = {}
for i in range(10):
    tmp[i] = lambda: i
 
>>> tmp[0]()
9
>>> tmp[1]()
9

Странное поведение данной конструкции связано с тем, что в питоне функции являются замыканиями (в том числе и lambda - выражение). Известно что замыкание это функция, которая хранит в себе ссылки на переменные вне своего блока. Получается что переменная i в пределах lambda функции является ссылкой на переменную i в цикле for. Поэтому в данном случае, когда меняется значение в переменной i в цикле for, во всех созданных lambda функциях значение переменной i в пределах lambda функции поменяется (в нашем случае вернется значение i-1). Что бы этого избежать можно явно передать в качестве параметра lambda функции переменную i:

tmp = {}
for i in range(10):
    tmp[i] = lambda i=i: i
 
>>> tmp[0]()
0
>>> tmp[1]()
1

Второй пример немного проще (взято отсюда)

>>> a = [[]] * 3 # создаем три вложенных списка
# хотим добавить в каждый вложенный список по букве
>>> a[0].append('a')
>>> a[1].append('b')
>>> a[2].append('c')
>>> print a
[['a', 'b', 'c'], ['a', 'b', 'c'], ['a', 'b', 'c']] # WAT?!

Почему так происходит? Опять же из-за ссылок. Давайте разберем еще два примера:

>>> a = [[], [], []]
>>> print [hex(id(i)) for i in a]
['0x10c04c9e0', '0x10c04c950', '0x10c04ca70']
 
>>> a = [[]] * 3
>>> print [hex(id(i)) for i in a]
['0x10c04c710', '0x10c04c710', '0x10c04c710']

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

Дополнительный материал

Python. Неочевидное поведение некоторых конструкций
Неочевидное поведение некоторых конструкций