""" Урок 62. Вложенные функции. Замыкание — весь код примеров одним файлом. Источник: subjects/python-fundamentals/course/lessons/62-closures/examples.html Файл собран автоматически (tools/build_lesson_examples.py): правьте страницу урока. Запуск: python lesson-62.py """ # ==================================================================== # Пример 1. Вложенная вспомогательная функция # ==================================================================== def process_data(data: list[str]) -> list[str]: def clean_text(text: str) -> str: return text.strip().lower() return [clean_text(item) for item in data] items = [" Apple ", " BaNaNa ", " CHERRY "] print(process_data(items)) # ['apple', 'banana', 'cherry'] # ==================================================================== # Пример 2. Правило LEGB на всех четырёх уровнях # ==================================================================== name = "GLOBAL" def outer_all(): name = "ENCLOSING" def inner_all(): name = "LOCAL" print("1) inner_all():", name) inner_all() outer_all() # 1) inner_all(): LOCAL def outer_no_local(): name = "ENCLOSING" def inner_no_local(): # локальной name здесь нет -> поиск идёт на уровень выше, в Enclosing print("2) inner_no_local():", name) inner_no_local() outer_no_local() # 2) inner_no_local(): ENCLOSING def outer_no_enclosing(): # локальной name во внешней функции тоже нет -> поиск идёт в Global def inner_no_enclosing(): print("3) inner_no_enclosing():", name) inner_no_enclosing() outer_no_enclosing() # 3) inner_no_enclosing(): GLOBAL def outer_builtin(): def inner_builtin(): # у 'len' нет ни Local, ни Enclosing, ни Global версии -> берётся Built-in print("4) inner_builtin():", len) inner_builtin() outer_builtin() # 4) inner_builtin(): # ==================================================================== # Пример 3. Замыкание, которое только читает внешнюю переменную # ==================================================================== def create_filter(border: float): def filter_value(value: float) -> bool: return value > border return filter_value greater_than_five = create_filter(5) print(greater_than_five(7)) # True print(greater_than_five(3)) # False # ==================================================================== # Пример 4. Замыкание с изменением состояния: nonlocal # ==================================================================== 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 # ==================================================================== # Пример 5. global в обычной функции vs nonlocal во вложенной # ==================================================================== total_visits = 0 def register_visit() -> int: global total_visits total_visits += 1 return total_visits print("global:", register_visit()) # global: 1 print("global:", register_visit()) # global: 2 def make_session_counter(): visits = 0 def register() -> int: nonlocal visits visits += 1 return visits return register session_a = make_session_counter() print("nonlocal:", session_a()) # nonlocal: 1 print("nonlocal:", session_a()) # nonlocal: 2 # ==================================================================== # Пример 6. Ловушка: global во вложенной функции указывает не туда # ==================================================================== count = 0 # модульная переменная, случайно совпадает по имени с локальной ниже def make_counter_trap(): count = 0 # предполагается "своя" переменная фабрики def increment_trap() -> int: global count # ЛОВУШКА: указывает на count модуля, а не на count фабрики count += 1 return count return increment_trap trap_counter = make_counter_trap() print("trap call 1:", trap_counter()) # trap call 1: 1 print("trap call 2:", trap_counter()) # trap call 2: 2 print("module-level count изменился:", count) # module-level count изменился: 2 # ==================================================================== # Пример 7. Фабрика функций умножения # ==================================================================== 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 # ==================================================================== # Пример 8. Альтернатива замыканию — functools.partial # ==================================================================== from functools import partial from operator import mul double = partial(mul, 2) triple = partial(mul, 3) print(double(4)) # 8 print(triple(4)) # 12 # ==================================================================== # Пример 9. Ловушка позднего связывания # ==================================================================== # Неправильно functions_wrong = [lambda x: x * i for i in range(3)] print([f(2) for f in functions_wrong]) # [4, 4, 4] # Правильно: захватываем текущее i через значение по умолчанию functions_right = [lambda x, i=i: x * i for i in range(3)] print([f(2) for f in functions_right]) # [0, 2, 4] # ==================================================================== # Пример 10. Как посмотреть замыкание изнутри: __closure__ # ==================================================================== def make_multiplier(factor: float): def multiply(number: float) -> float: return number * factor return multiply double = make_multiplier(2) print(double.__closure__) # (,) print(double.__closure__[0].cell_contents) # 2 # ==================================================================== # Пример 11. Счётчик вызовов: global-переменная vs замыкание # ==================================================================== # вариант 1: счётчик через глобальную переменную click_count = 0 def track_click() -> None: global click_count click_count += 1 print(f"click #{click_count}") track_click() # click #1 track_click() # click #2 # Недостаток: track_click "жёстко" привязан к click_count. Чтобы завести # второй независимый счётчик, пришлось бы копировать и переменную, и функцию. # вариант 2: счётчик через замыкание (та же идея, что в примере 4) def make_counter(): count = 0 def increment() -> int: nonlocal count count += 1 return count return increment counter_a = make_counter() counter_b = make_counter() print("counter_a:", counter_a()) # counter_a: 1 print("counter_a:", counter_a()) # counter_a: 2 print("counter_b:", counter_b()) # counter_b: 1 — независим от counter_a