Замыкания и особенности их применения Python

Замыкания и особенности их применения

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

Замыкания

Давайте напишем простейшее замыкание, чтобы разобраться, как они работают.

1
2
3
4
5
def set_surname(surname):
    surname = ' ' + surname
    def inner(name):
        return name + surname
    return inner

Внешняя функция 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
def set_names(names):
    results = []
    for name in names:
        def inner(surname):
            return name + ' ' + surname
        results.append(inner)
    return results

Нам хотелось бы, чтобы эта функция работала таким образом:

>>> 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
def set_names(names):
    results = []
    for name in names:
        def inner(surname, name=name):
            return name + ' ' + surname
        results.append(inner)
    return results

Теперь нужные значения записываются как значения по умолчанию, отдельно для каждого замыкания, что решает нашу проблему. С другой стороны, свободная переменная name становится аргументом, из-за чего ее можно изменить. Если такое поведение для вас нежелательно — придется воспользоваться частичным применением функции.

1
2
3
4
5
6
7
8
from functools import partial
def set_names(names):
    results = []
    for name in names:
        def inner(first_name, surname):
            return first_name + ' ' + surname
        results.append(partial(inner, name))
    return results

Функция partial из модуля functools позволила нам установить first_name равным name в момент своего вызова. Это поможет избежать нежелательных изменений замыкания.

Заключение

При грамотном использовании, замыкания могут стать мощным инструментом. Однако использовать их нужно с осторожностью, памятуя о позднем связывании в Python. Часто, замыкания можно заменить частичным применением функции или функтором. Не всегда самое простое решение будет оптимальным.

Практический Python для начинающих
Практический Python для начинающих

Станьте junior Python программистом за 7 месяцев

 7 месяцев

Возможно будет интересно

🏆 Hello, world! Python
Новичок
🏆 Hello, world!

Мы вчера запустили новый www.pylot.me. Должны были в следующую среду, но запустили вчера.

2022-10-04
Как практиковаться в Python? Python
Новичок
Как практиковаться в Python?

Для улучшения качества знаний и повышения уровня программиста, необходим постоянный практикум. Где можно это организовать самостоятельно, и как практиковаться в Python?

2022-10-19
Условные конструкции и сопоставление структурных шаблонов Шпаргалки
Новичок
Условные конструкции и сопоставление структурных шаблонов

Шпаргалка по условным конструкциям и сопоставлению структурных шаблонов

2022-11-09