Сможете ли вы решить эти 3 «простые» задачи на Python?



Книга Сможете ли вы решить эти 3 «простые» задачи на Python?

Правильное решение вас удивит


Попробуйте решить эти задачи самостоятельно, а потом проверьте себя по готовым ответам.


Подсказка: У всех задач есть нечто общее. Так что если разобраться в решении первой задачи, то решать остальные будет гораздо проще.


Задача 1


Представьте, что у вас есть несколько переменных:


x = 1
y = 2
l = [x, y]
x += 5

a = [1]
b = [2]
s = [a, b]
a.append(5)

Какой результат вам даст вывод l и s?


Задача 2


Давайте определим простую функцию:


def f(x, s=set()):
s.add(x)
print(s)

Что произойдет при вызове:


>>f(7)
>>f(6, {4, 5})
>>f(2)

?


Задача 3


Давайте определим две простые функции:


def f():
l = [1]
def inner(x):
l.append(x)
return l
return inner

def g():
y = 1
def inner(x):
y += x
return y
return inner

Какой результат вы получите при выполнении следующих команд?


>>f_inner = f()
>>print(f_inner(2))

>>g_inner = g()
>>print(g_inner(2))

Насколько вы уверены в своих ответах? Давайте узнаем правильное решение.


Решение задачи 1


>>print(l)
[1, 2]

>>print(s)
[[1, 5], [2]]

Почему второй список реагирует на изменение своего первого элемента a.append(5), а первый список полностью игнорирует похожее изменение x+=5?


Решение задачи 2


Давайте узнаем, что произойдет:


>>f(7)
{7}

>>f(6, {4, 5})
{4, 5, 6}

>>f(2)
{2, 7}

Стоп, а разве последний вывод не должен быть {2}?


Решение задачи 3


Результаты будут следующими:


>>f_inner = f()
>>print(f_inner(2))
[1, 2]

>>g_inner = g()
>>print(g_inner(2))
UnboundLocalError: local variable ‘y’ referenced before assignment

Но почему g_inner(2) не выводит 3? Как так вышло, что внутренняя функция f() запоминает свою внешнюю область видимости, а внутренняя функция g() — нет? Они же практически одинаковые!


Объяснение


Что, если я скажу вам, что столь странное поведение обусловлено разницей между изменяемыми и неизменяемыми объектами в Python?


Изменяемые объекты (списки, наборы или словари) могут изменяться (мутировать) на месте. Неизменяемые объекты (целые числа, строки и кортежи) не могут изменяться. «Изменение» таких объектов приводит к созданию нового объекта.


Объяснение задачи 1


x = 1
y = 2
l = [x, y]
x += 5

a = [1]
b = [2]
s = [a, b]
a.append(5)

>>print(l)
[1, 2]

>>print(s)
[[1, 5], [2]]

Поскольку x является неизменяемым объектом, операция x+=5 не меняет исходный объект, а создает новый. Первый элемент списка все еще указывает на первоначальный объект, поэтому его значение остается прежним.


a — это изменяемый объект. Поэтому a.append(5) изменяет исходный объект, а список s «видит» эти изменения.


Объяснение задачи 2


def f(x, s=set()):
s.add(x)
print(s)

>>f(7)
{7}

>>f(6, {4, 5})
{4, 5, 6}

>>f(2)
{2, 7}

Первые два результата очевидны: сначала значение 7 добавляется к пустому множеству по умолчанию, и в результате получается {7}. Потом значение 6 добавляется к набору {4, 5}, и на выходе получается {4, 5, 6}.


Но затем происходит нечто странное: значение 2 добавляется не к стандартному пустому множеству, а к набору {7}. Почему? Стандартное значение необязательного параметра s вычисляется только один раз, ведь только при первом вызове s запускается как пустое множество. А поскольку s является изменяемым объектом, то после вызова f(7) он изменяется на месте. Второй вызов f(6, {4, 5}) не влияет на исходный параметр, поскольку представленное множество {4, 5} скрывает его. Иначе говоря, {4, 5} является другой переменной. Третий вызов f(2) использует ту же переменную s, которая была в первом вызове. Но в этот раз s вызывается не как пустое множество, а со своим предыдущим значением {7}.


Вот почему вам не следует использовать изменяемые аргументы по умолчанию. В таком случае функция будет изменяться следующим образом:


def f(x, s=None):
if s is None:
s = set()
s.add(x)
print(s)

Объяснение задачи 3


def f():
l = [1]
def inner(x):
l.append(x)
return l
return inner

def g():
y = 1
def inner(x):
y += x
return y
return inner

>>f_inner = f()
>>print(f_inner(2))
[1, 2]

>>g_inner = g()
>>print(g_inner(2))
UnboundLocalError: local variable ‘y’ referenced before assignment

В этой задаче мы имеем дело с замыканием, при котором внутренние функции запоминают, как выглядело их внешнее пространство имен в момент определения. По крайней мере, они должны это делать. А вторая функция с завидной невозмутимостью делает вид, что никогда не слышала про внешнюю область видимости.


Почему так? Когда мы выполняем l.append(x), изменяемый объект, созданный в момент определения, изменяется, но переменная продолжает указывать на тот же адрес в памяти. Однако попытка изменить неизменяемую переменную во второй функции y += x приводит к тому, что y начинает указывать на другой адрес в памяти. Исходная y больше не запоминается, и возникает ошибка UnboundLocalError.


Заключение


В Python очень важно разграничивать изменяемые и неизменяемые объекты. Во избежание странного поведения кода (как в примерах выше) нужно помнить о главном:


  • не используйте изменяемые аргументы по умолчанию;
  • не пытайтесь изменять неизменяемые замкнутые переменные во внутренних функциях.

471   0  

Comments

    Ничего не найдено.