Когда подводный камень вовсе не подводный камень. Иногда вы можете намеренно задействовать (то есть использовать в качестве нормального варианта поведения) этот подводный камень, чтобы сохранять состояние между вызовами функции. Зачастую это делается при написании функции кэширования (которая сохраняет результаты в памяти), например: def time_consuming_function(x, y, cache={}):
args = (x, y)
if args in cache:
return cache[args]
# В противном случае функция работает с аргументами в первый раз.
# Выполняем сложную операцию...
cache[args] = result
return result
Замыкания с поздним связыванием
Еще один распространенный источник путаницы — способ связывания переменных в замыканиях (или в окружающей глобальной области видимости).
Что вы написали:
def create_multipliers():
return [lambda x : i * x for i in range(5)]
Чего вы ожидаете:
for multiplier in create_multipliers():
print(multiplier(2), end=" ... ")
print()
Список, содержащий пять функций, каждая из них имеет собственную замкнутую переменную i, которая умножается на их аргумент, что приводит к получению следующего результата: 0 ... 2 ... 4 ... 6 ... 8 ...
Что происходит на самом деле:
8 ... 8 ... 8 ... 8 ... 8 ...
Создаются пять функций, все они умножают х на 4. Почему? В Python замыкания имеют
В нашем примере, когда вызывается
Особенно неудобно то, что вам может показаться, будто ошибка как-то связана с лямбда-выражениями (https://docs.python.org/3/tutorial/controlflow.html#lambda-expressions). Функции, создаваемые с помощью лямбда-выражений, не отличаются от других. Фактически то же самое поведение проявляется и при использовании самого обычного def: def create_multipliers():
multipliers = []
for i in range(5):
def multiplier(x):
return i * x
multipliers.append(multiplier)
return multipliers
Что вам нужно сделать вместо этого? Наиболее общее решение, возможно, станет «костылем» — временным вариантом устранения проблемы. Из-за уже упомянутого поведения Python, связанного с определением аргументов по умолчанию для функций (см. предыдущий пункт «Изменяемые аргументы функций»), вы можете создать замыкание, которое немедленно связывается со своими аргументами с помощью аргумента по умолчанию: def create_multipliers():
return [lambda x, i=i : i * x for i in range(5)]
Помимо этого вы можете использовать функцию functools.partial():
from functools import partial
from operator import mul
def create_multipliers():
return [partial(mul, i) for i in range(5)]
Когда подводный камень вовсе не подводный камень. Иногда нужно, чтобы замыкания вели себя подобным образом. Позднее связывание может быть полезным во многих ситуациях (например, в проекте Diamond, см. пункт «Пример использования замыкания (когда подводный камень вовсе не подводный камень)» на с. 136). Наличие уникальных функций в циклах, к сожалению, может привести к сбоям.
Структурируем проект
Под
По какому принципу функции должны размещаться в модулях? Как данные перемещаются по проекту? Какие функции могут быть сгруппированы и изолированы? Отвечая на эти вопросы, вы можете запланировать, как будет выглядеть ваш конечный продукт.
В книге
Благодаря тому, как в Python налажен процесс импортирования и разбиения на модули, структурировать проект довольно просто: существует всего несколько ограничений, модель для импортирования также нетрудно освоить. Поэтому перед вами стоит исключительно архитектурная задача — создать различные части проекта и продумать их взаимодействие.
Модули