Вложенные функции
В Python одна функция может быть определена внутри другой. Внутренняя функция называется вложенной (nested). Она полезна, когда нужно спрятать вспомогательную логику внутри основной функции.
def outer_function():
print("Inside outer function")
def inner_function():
print("Inside inner function")
inner_function()
outer_function()
# inner_function() # NameError: name 'inner_function' is not defined
Зачем нужны вложенные функции
- Инкапсуляция логики — вспомогательный код спрятан внутри основной функции.
- Локальная область видимости — переменные вложенной функции не мешают глобальному пространству имён.
- Избегание дублирования — один и тот же вспомогательный код можно вызывать несколько раз внутри внешней функции.
Область видимости Enclosing и LEGB
Python ищет имена в следующем порядке:
- Local — локальная область текущей функции.
- Enclosing — ближайшая внешняя функция, если текущая вложена.
- Global — глобальная область модуля.
- Built-in — встроенные имена Python.
def outer_function(repeat: int) -> None:
message = "Outer function\n"
def inner_function() -> None:
print(message * repeat) # переменные из Enclosing-области
inner_function()
outer_function(3)
Локальные переменные затеняют внешние
Если во вложенной функции присвоить значение имени, совпадающему с именем во внешней функции, создаётся новая локальная переменная. Внешняя переменная не изменится.
def outer_function() -> None:
message = "Outer function"
def inner_function() -> None:
message = "Inner function" # новая локальная переменная
inner_function()
print(message) # Outer function
outer_function()
Ключевое слово nonlocal
Если нужно изменить переменную внешней функции изнутри вложенной, используйте nonlocal. Оно указывает, что имя принадлежит ближайшей Enclosing-области, а не локальной.
def outer_function() -> None:
message = "Outer function"
def inner_function() -> None:
nonlocal message
message = "Changed inside inner function"
inner_function()
print(message) # Changed inside inner function
outer_function()
nonlocal работает только для ближайшей внешней функции. Оно не поднимается до глобальной области. Для глобальных переменных используйте global.
Замыкания
Замыкание (closure) — это объект, содержащий функцию и сохранённое окружение: переменные из Enclosing-области, которые остаются доступными даже после завершения внешней функции.
def outer_function(text: str):
def inner_function() -> None:
print(text) # захваченная переменная
return inner_function
closure = outer_function("Captured text")
print(closure) # <function outer_function.<locals>.inner_function ...>
closure() # Captured text
Свободные переменные
Переменные, которые используются во вложенной функции, но определены во внешней, называются свободными переменными (free variables). Именно они хранятся в замыкании.
def make_counter():
count = 0
def increment() -> int:
nonlocal count
count += 1
return count
return increment
counter = make_counter()
print(counter()) # 1
print(counter()) # 2
print(counter()) # 3
Фабрики функций
Замыкания часто используются для создания семейства похожих функций с разными настройками. Такой приём называется фабрикой функций.
def multiplier(factor: float):
def multiply(number: float) -> float:
return number * factor
return multiply
double = multiplier(2)
triple = multiplier(3)
print(double(4)) # 8
print(triple(4)) # 12
Ловушка позднего связывания
Самая частая ошибка с замыканиями — создавать их внутри цикла, не передавая текущее значение переменной внутрь. Python связывает имя переменной, а не её значение, поэтому все замыкания получат последнее значение цикла.
def make_multipliers_wrong():
return [lambda x: x * i for i in range(5)]
for func in make_multipliers_wrong():
print(func(2)) # 8 8 8 8 8, а не 0 2 4 6 8
Исправление — захватить текущее значение через параметр по умолчанию:
def make_multipliers_right():
return [lambda x, i=i: x * i for i in range(5)]
for func in make_multipliers_right():
print(func(2)) # 0 2 4 6 8
Функция как объект
В Python функция — это объект, у которого есть служебные атрибуты. К ним можно обращаться напрямую.
def greet(name: str) -> None:
"""Greeting function."""
print(f"Hello, {name}!")
print(greet.__name__) # greet
print(greet.__doc__) # Greeting function.
def make_multiplier(factor: float):
def multiply(number: float) -> float:
return number * factor
return multiply
double = make_multiplier(2)
print(double.__closure__) # (<cell at ...>,)
print(double.__closure__[0].cell_contents) # 2
Декораторы как частный случай замыканий
Декоратор — это функция, которая принимает другую функцию, добавляет к ней поведение и возвращает новую функцию. Внутри декоратора обычно используется вложенная функция-обёртка, поэтому декораторы — прямое применение замыканий.
def simple_decorator(func):
def wrapper():
print("Before call")
func()
print("After call")
return wrapper
@simple_decorator
def say_hello():
print("Hello!")
say_hello()
Синтаксис @simple_decorator эквивалентен записи say_hello = simple_decorator(say_hello). Обёртка wrapper захватывает func в замыкании.
functools.wraps — в глоссарии Python.