Вложенные функции Python: назначение, синтаксис и практические сценарии
Локальные функции в Python: базовый подход и варианты
Локальная функция Python определяется внутри другой функции. Она доступна только в теле внешней функции, если не возвращается наружу как объект. Такой прием помогает группировать вспомогательную логику, создавать замыкания, декораторы и фабрики функций.
Основной вариант: вложенная функция определяется в теле внешней функции и вызывается там же либо возвращается как результат.
def outer(x):
def inner(y):
return x + y
return inner(5)
print(outer(10))локальная функция python (локальные функции (вложенные) в python)
15
Внешняя функция outer принимает x. Внутренняя inner использует x из enclosing scope и y из своей области. Вызов inner(5) происходит внутри outer, поэтому результат возвращается наружу.
Как вернуть вложенную функцию и сохранить доступ к внешней переменной?
Если inner возвращается без вызова, получается замыкание. Оно запоминает значение x из момента создания.
def make_adder(n):
def add(x):
return x + n
return add
add5 = make_adder(5)
print(add5(3))
print(add5(10))
8 15
Функция make_adder создает новую функцию add для каждого n. Переменная n хранится в ячейке замыкания.
Как изменить переменную внешней функции из вложенной?
Если вложенная функция присваивает значение переменной, Python считает ее локальной для inner. Для изменения переменной внешней функции применяется nonlocal.
def counter():
count = 0
def increment():
nonlocal count
count += 1
return count
return increment
c = counter()
print(c())
print(c())
1 2
Без nonlocal возникла бы UnboundLocalError, потому что count += 1 создает локальную переменную count до ее чтения.
Как создать несколько независимых счетчиков через фабрику функций?
Каждый вызов внешней функции создает новое окружение и новую ячейку count.
def make_counter(start=0):
value = start
def step():
nonlocal value
value += 1
return value
return step
c1 = make_counter(10)
c2 = make_counter(100)
print(c1())
print(c2())
print(c1())
11 101 12
Как вложенные функции применяются в декораторах?
Декоратор обычно содержит функцию wrapper, которая вызывает исходную функцию.
def trace(func):
def wrapper(*args, **kwargs):
print('Call', func.__name__)
result = func(*args, **kwargs)
print('Result', result)
return result
return wrapper
@trace
def add(a, b):
return a + b
print(add(2, 3))
Call add Result 5 5
wrapper замыкает func. Декоратор возвращает wrapper вместо исходной функции.
Как избежать позднего связывания в замыканиях внутри цикла?
Функции в списке могут захватывать одну и ту же переменную i. Значение читается при вызове, а не при создании. Для фиксации текущего значения применяется аргумент по умолчанию или фабрика.
funcs = []
for i in range(3):
funcs.append(lambda: i)
print([f() for f in funcs])
funcs2 = []
for i in range(3):
funcs2.append(lambda i=i: i)
print([f() for f in funcs2])
[2, 2, 2] [0, 1, 2]
Аргумент i=i сохраняет текущее значение в момент создания lambda. Это классическая ошибка позднего связывания.
Как организовать рекурсию через вложенную функцию?
Вложенная функция может вызывать себя по имени или через параметр. Для самовызова имя ищется в enclosing scope.
def factorial(n):
def fact(k):
if k <= 1:
return 1
return k * fact(k - 1)
return fact(n)
print(factorial(5))
120
Внешняя функция служит оберткой, а вся рекурсивная логика скрыта внутри.
Как использовать вложенную функцию для мемоизации?
Кэш можно хранить во внешней функции, а вложенная функция будет обновлять его через nonlocal или через изменяемый объект.
def memo_fib():
cache = {}
def fib(n):
if n in cache:
return cache[n]
if n < 2:
cache[n] = n
else:
cache[n] = fib(n - 1) + fib(n - 2)
return cache[n]
return fib
fib = memo_fib()
print(fib(10))
print(fib(20))
55 6765
Как проверить область видимости вложенной функции?
Атрибут __closure__ показывает ячейки, связанные с замыканием. Атрибут __code__.co_freevars перечисляет свободные переменные.
def outer(a):
b = 2
def inner(c):
return a + b + c
return inner
f = outer(1)
print(f.__code__.co_freevars)
print(f.__closure__)
print(f(3))
('a', 'b')
(, | )
6 | |
Значения ячеек можно получить через cell_contents, но для прикладного кода обычно достаточно понимания freevars.
Типичная ошибка: UnboundLocalError при попытке изменить внешнюю переменную без nonlocal.
def outer():
x = 1
def inner():
x += 1
return x
return inner()
# print(outer()) # UnboundLocalError
Решение: объявить nonlocal x внутри inner. Если переменная находится в глобальной области, применяется global.
Проблема читаемости: глубокая вложенность усложняет отладку и тестирование. Вложенные функции уместны для коротких помощников, замыканий и декораторов. Для крупных блоков логики лучше вынести функцию на уровень модуля или класса.
Ограничение pickle: локальные функции и lambda нельзя сериализовать стандартным pickle, если они не доступны по глобальному имени. Для передачи между процессами часто применяются функции верхнего уровня.
Расширенные примеры вложенных функций Python
Фабрика валидаторов с сообщением об ошибке
Внешняя функция создает валидатор для конкретного поля. Внутренняя функция замыкает min_len и field.
def make_length_validator(field, min_len):
def validate(value):
if len(value) < min_len:
return f'{field}: минимум {min_len} символов'
return None
return validate
validate_name = make_length_validator('name', 3)
validate_code = make_length_validator('code', 5)
print(validate_name('ab'))
print(validate_code('abc'))
print(validate_name('alice'))
name: минимум 3 символов code: минимум 5 символов None
Каждый валидатор хранит собственные field и min_len. Такой прием полезен в формах и API.
Декоратор с параметрами через вложенные функции
Внешняя функция принимает параметры декоратора, средняя получает функцию, внутренняя выполняет вызов.
def repeat(times):
def decorator(func):
def wrapper(*args, **kwargs):
result = None
for _ in range(times):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def greet(name):
print('Hello', name)
return name
print(greet('Anna'))
Hello Anna Hello Anna Hello Anna Anna
Три уровня вложенности типичны для декораторов с аргументами.
Аккумулятор с несколькими действиями и nonlocal
Внутренние функции изменяют общее состояние через nonlocal.
def make_account(balance=0):
def deposit(amount):
nonlocal balance
balance += amount
return balance
def withdraw(amount):
nonlocal balance
if amount > balance:
return 'Недостаточно средств'
balance -= amount
return balance
def get_balance():
return balance
return deposit, withdraw, get_balance
dep, wd, get = make_account(100)
print(dep(50))
print(wd(30))
print(get())
150 120 120
Все три функции замыкают одну ячейку balance.
Вложенная функция как ключ сортировки
Локальная функция может ссылаться на внешние параметры сортировки.
def sort_by_weight(items, weights):
def key(item):
return weights.get(item, 0)
return sorted(items, key=key)
items = ['b', 'a', 'c']
weights = {'a': 10, 'b': 5, 'c': 1}
print(sort_by_weight(items, weights))
['c', 'b', 'a']
key замыкает weights и применяется только внутри sort_by_weight.
Рекурсивный поиск во вложенных словарях
Внутренняя функция рекурсивно обходит структуру и накапливает путь.
def find_key(data, target):
def walk(node, path):
if isinstance(node, dict):
for k, v in node.items():
new_path = path + [k]
if k == target:
return new_path
found = walk(v, new_path)
if found:
return found
elif isinstance(node, list):
for index, item in enumerate(node):
found = walk(item, path + [index])
if found:
return found
return None
return walk(data, [])
data = {'a': {'b': [{'c': 1}]}}
print(find_key(data, 'c'))
['a', 'b', 0, 'c']
Внешняя функция задает начальный путь, а walk скрывает детали обхода.
Генератор, создаваемый вложенной функцией
Вложенная функция может быть генератором и возвращаться как итератор.
def make_range_generator(start, stop):
def gen():
current = start
while current < stop:
yield current
current += 1
return gen
gen = make_range_generator(2, 5)
print(list(gen()))
[2, 3, 4]
start и stop остаются доступными во время итерации.
Замыкание для обработки событий
Вложенная функция запоминает идентификатор события и не зависит от внешнего цикла.
def make_handlers(event_ids):
handlers = []
for event_id in event_ids:
def handler(event_id=event_id):
return f'Обработчик {event_id}'
handlers.append(handler)
return handlers
for h in make_handlers([10, 20, 30]):
print(h())
Обработчик 10 Обработчик 20 Обработчик 30
Аргумент по умолчанию фиксирует текущее значение event_id.
Кэширование с ручным замыканием и подсчетом вызовов
Внешняя функция хранит cache и calls. Внутренняя функция обновляет их.
def memo_square():
cache = {}
calls = 0
def square(n):
nonlocal calls
calls += 1
if n not in cache:
cache[n] = n * n
return cache[n]
def stats():
return calls, len(cache)
return square, stats
square, stats = memo_square()
print(square(4))
print(square(4))
print(square(5))
print(stats())
16 16 25 (3, 2)
Функция stats показывает, что вычисление square выполнялось три раза, а уникальных значений два.
Вложенная функция в методе класса
Метод может создавать локальную функцию для обработки данных и не раскрывать ее в интерфейсе класса.
class Report:
def __init__(self, rows):
self.rows = rows
def total(self):
def to_number(value):
return float(value.replace(',', '.'))
return sum(to_number(row['amount']) for row in self.rows)
report = Report([{'amount': '1,5'}, {'amount': '2,25'}])
print(report.total())
3.75
to_number нужна только внутри total и не засоряет пространство имен класса.