[Python, Программирование] А вы можете решить эти три (обманчиво) простые задачи на Python? (перевод)
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
С самого начала своего пути как разработчика программного обеспечения я очень любил копаться во внутренностях языков программирования. Мне всегда было интересно, как устроена та или иная конструкция, как работает та или иная команда, что под капотом у синтаксического сахара и т.п. Недавно мне на глаза попалась интересная статья с примерами того, как не всегда очевидно работают mutable- и immutable-объекты в Python. На мой взгляд, ключевое — это то, как меняется поведение кода в зависимости от используемого типа данных, при сохранении идентичной семантики и используемых языковых конструкциях. Это отличный пример того, что думать надо не только при написании, но и при использовании. Предлагаю всем желающим ознакомиться с переводом.
Попробуйте решить эти три задачи, а потом сверьтесь с ответами в конце статьи.
Совет: у задач есть кое-что общее, поэтому освежите в памяти решение первой задачи, когда перейдёте ко второй или третьей, так вам будет проще.
Первая задача
Есть несколько переменных:
x = 1
y = 2
l = [x, y]
x += 5
a = [1]
b = [2]
s = [a, b]
a.append(5)
Что будет выведено на экран при печати l и s?
Вторая задача
Определим простую функцию:
def f(x, s=set()):
s.add(x)
print(s)
Что будет, если вызвать:
>>f(7)
>>f(6, {4, 5})
>>f(2)
Третья задача
Определим две простые функции:
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))
Насколько вы уверены в своих ответах? Давайте проверим вашу правоту.
Решение первой задачи
>>print(l)
[1, 2]
>>print(s)
[[1, 5], [2]]
Почему второй список реагирует на изменение своего первого элемента a.append(5), а первый список полностью игнорирует такое же изменение x+=5?
Решение второй задачи
Посмотрим, что произойдёт:
>>f(7)
{7}
>>f(6, {4, 5})
{4, 5, 6}
>>f(2)
{2, 7}
Погодите, разве последним результатом не должно быть {2}?
Решение третьей задачи
Результат будет таким:
>>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?
Изменяемые объекты, такие как списки, множества или словари, могут быть изменены на месте. Неизменяемые объекты, такие как числовые и строковые значения, кортежи, не могут быть изменены; их «изменение» приведёт к созданию новых объектов.
Объяснение первой задачи
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 «видел» бы изменение.
Объяснение второй задачи
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 будет инициализировано как пустое множество. А поскольку оно изменяемое, после вызова f(7) оно будет будет изменено “на месте”. Второй вызов f(6, {4, 5}) не повлияет на параметр по умолчанию: его заменяет множество {4, 5}, то есть {4, 5} является другой переменной. Третий вызов f(2) использует ту же переменную s, что использовалась при первом вызове, но она не переинициализируется как пустое множество, а вместо этого берётся её предыдущее значение {7}.
Поэтому не следует использовать изменяемые аргументы в качестве аргументов по умолчанию. В этом случае функцию нужно изменить:
def f(x, s=None):
if s is None:
s = set()
s.add(x)
print(s)
Объяснение третьей задачи
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), меняется изменяемый объект, созданный при определении функции. Но переменная l всё ещё ссылается на старый адрес в памяти. Однако попытка изменить неизменяемую переменную во второй функции y += x приводит к тому, что y начинает ссылаться на другой адрес в памяти: исходная y будет забыта, что приведёт к ошибке UnboundLocalError.
Заключение
Разница между изменяемыми и неизменяемыми объектами в Python очень важна. Избегайте странного поведения, описанного в этой статье. В особенности:
- Не используйте по умолчанию изменяемые аргументы.
- Не пытайтесь менять неизменяемые переменные-замыкания во внутренних функциях.
===========
Источник:
habr.com
===========
===========
Автор оригинала: Maria Fabiańska
===========Похожие новости:
- [DevOps, Python, Машинное обучение, Управление разработкой] MLOps — Cook book, chapter 1
- [Python, Микросервисы, Программирование, Тестирование веб-сервисов] Функциональные тесты в Циан
- [Open source, Python] Ищем фильмы, книги и подкасты с помощью Python
- [JavaScript, jQuery, Программирование, Разработка веб-сайтов] Как я в 15 лет написал свой первый jQuery плагин и как их создавать
- [DIY или Сделай сам, Программирование микроконтроллеров, Производство и разработка электроники, Разработка робототехники, Схемотехника] Запускаем камеру от телефона, или что делать, когда ничего не получается?
- [C, C++, Программирование, Реверс-инжиниринг] IDA Pro: работа с библиотечным кодом (не WinAPI)
- [Алгоритмы, Программирование, Процессоры] Разбираемся в моделях кода архитектуры x64 (перевод)
- [.NET, C#, Программирование] Самые простые конечные автоматы или стейт-машины в три шага
- [Микросервисы, Программирование] Микросервисы: шаг назад
- [.NET, C#, Программирование] Магические сигнатуры методов в C# (перевод)
Теги для поиска: #_python, #_programmirovanie (Программирование), #_python, #_python3, #_mutable, #_immutable, #_blog_kompanii_domklik (
Блог компании ДомКлик
), #_python, #_programmirovanie (
Программирование
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 15:09
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
С самого начала своего пути как разработчика программного обеспечения я очень любил копаться во внутренностях языков программирования. Мне всегда было интересно, как устроена та или иная конструкция, как работает та или иная команда, что под капотом у синтаксического сахара и т.п. Недавно мне на глаза попалась интересная статья с примерами того, как не всегда очевидно работают mutable- и immutable-объекты в Python. На мой взгляд, ключевое — это то, как меняется поведение кода в зависимости от используемого типа данных, при сохранении идентичной семантики и используемых языковых конструкциях. Это отличный пример того, что думать надо не только при написании, но и при использовании. Предлагаю всем желающим ознакомиться с переводом. Попробуйте решить эти три задачи, а потом сверьтесь с ответами в конце статьи. Совет: у задач есть кое-что общее, поэтому освежите в памяти решение первой задачи, когда перейдёте ко второй или третьей, так вам будет проще. Первая задача Есть несколько переменных: x = 1
y = 2 l = [x, y] x += 5 a = [1] b = [2] s = [a, b] a.append(5) Что будет выведено на экран при печати l и s? Вторая задача Определим простую функцию: def f(x, s=set()):
s.add(x) print(s) Что будет, если вызвать: >>f(7)
>>f(6, {4, 5}) >>f(2) Третья задача Определим две простые функции: 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)) Насколько вы уверены в своих ответах? Давайте проверим вашу правоту. Решение первой задачи >>print(l)
[1, 2] >>print(s) [[1, 5], [2]] Почему второй список реагирует на изменение своего первого элемента a.append(5), а первый список полностью игнорирует такое же изменение x+=5? Решение второй задачи Посмотрим, что произойдёт: >>f(7)
{7} >>f(6, {4, 5}) {4, 5, 6} >>f(2) {2, 7} Погодите, разве последним результатом не должно быть {2}? Решение третьей задачи Результат будет таким: >>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? Изменяемые объекты, такие как списки, множества или словари, могут быть изменены на месте. Неизменяемые объекты, такие как числовые и строковые значения, кортежи, не могут быть изменены; их «изменение» приведёт к созданию новых объектов. Объяснение первой задачи 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 «видел» бы изменение. Объяснение второй задачи 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 будет инициализировано как пустое множество. А поскольку оно изменяемое, после вызова f(7) оно будет будет изменено “на месте”. Второй вызов f(6, {4, 5}) не повлияет на параметр по умолчанию: его заменяет множество {4, 5}, то есть {4, 5} является другой переменной. Третий вызов f(2) использует ту же переменную s, что использовалась при первом вызове, но она не переинициализируется как пустое множество, а вместо этого берётся её предыдущее значение {7}. Поэтому не следует использовать изменяемые аргументы в качестве аргументов по умолчанию. В этом случае функцию нужно изменить: def f(x, s=None):
if s is None: s = set() s.add(x) print(s) Объяснение третьей задачи 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), меняется изменяемый объект, созданный при определении функции. Но переменная l всё ещё ссылается на старый адрес в памяти. Однако попытка изменить неизменяемую переменную во второй функции y += x приводит к тому, что y начинает ссылаться на другой адрес в памяти: исходная y будет забыта, что приведёт к ошибке UnboundLocalError. Заключение Разница между изменяемыми и неизменяемыми объектами в Python очень важна. Избегайте странного поведения, описанного в этой статье. В особенности:
=========== Источник: habr.com =========== =========== Автор оригинала: Maria Fabiańska ===========Похожие новости:
Блог компании ДомКлик ), #_python, #_programmirovanie ( Программирование ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 15:09
Часовой пояс: UTC + 5