Замыкания и особенности их применения
Замыкания — функции, которые содержат ссылки на переменные, объявленные вне их тела. В этой статье мы разберемся, зачем могут понадобиться такие конструкции и какие подводные камни есть у использования замыканий.
Замыкания
Давайте напишем простейшее замыкание, чтобы разобраться, как они работают.
1 2 3 4 5 |
|
Внешняя функция set_surname
принимает один аргумент - surname
, и добавляет перед ней пробел. Внутренняя функция inner
будет заново определяться каждый раз, когда вызывается внешняя. Она принимает другой аргумент — name
, и возвращает строку из имени и фамилии. Такие переменные, как surname
, используемые в блоке кода, но объявленные за его пределами, называются свободными. И наконец, внешняя функция set_surname
возвращает inner
с подставленным в переменную surname
значением. Вот, что из этого выйдет:
>>> anderson = set_surname('Anderson')
... print(anderson('Helen'))
... print(anderson('Joe'))
...
Helen Anderson
Joe Anderson
Если разобраться, то в переменную anderson
была записана такая функция:
surname = ' Anderson'
def func(name):
return name + surname
И в этом кроется корень следующей проблемы.
Позднее связывание замыканий
Давайте попробуем написать замыкание, чтобы оценить, как сочетаются разные имена с фамилией новорожденного.
1 2 3 4 5 6 7 |
|
Нам хотелось бы, чтобы эта функция работала таким образом:
>>> names_lst = ['Roy', 'Jake', 'Colin']
... for child_name in set_names(names_lst):
... print(child_name('Robinson'))
Roy Robinson
Jake Robinson
Colin Robinson
Однако результат будет совершенно другим:
Colin Robinson
Colin Robinson
Colin Robinson
Виной тому позднее связывание. Это означает, что значения свободных переменных будут прочитаны замыканием в момент его вызова, иначе говоря — слишком поздно. Каждое новое значение name
перезаписывало старое, а затем каждое наше замыкание обратилось к последнему значению переменной name
. Эту проблему можно решить при помощи аргумента по умолчанию для внутренней функции:
1 2 3 4 5 6 7 |
|
Теперь нужные значения записываются как значения по умолчанию, отдельно для каждого замыкания, что решает нашу проблему. С другой стороны, свободная переменная name
становится аргументом, из-за чего ее можно изменить. Если такое поведение для вас нежелательно — придется воспользоваться частичным применением функции.
1 2 3 4 5 6 7 8 |
|
Функция partial
из модуля functools
позволила нам установить first_name
равным name
в момент своего вызова. Это поможет избежать нежелательных изменений замыкания.
Заключение
При грамотном использовании, замыкания могут стать мощным инструментом. Однако использовать их нужно с осторожностью, памятуя о позднем связывании в Python. Часто, замыкания можно заменить частичным применением функции или функтором. Не всегда самое простое решение будет оптимальным.
Возможно будет интересно
🏆 Hello, world!
Мы вчера запустили новый www.pylot.me. Должны были в следующую среду, но запустили вчера.
Как практиковаться в Python?
Для улучшения качества знаний и повышения уровня программиста, необходим постоянный практикум. Где можно это организовать самостоятельно, и как практиковаться в Python?
Условные конструкции и сопоставление структурных шаблонов
Шпаргалка по условным конструкциям и сопоставлению структурных шаблонов