Перейти к содержимому
Editor

Области видимости переменных и замыкания в Python: правило LEGB

Средний25 min5 упр.70 XP

Этот урок переведён с английского оригинала.Read in EnglishLeer en españolСообщить об ошибке в переводе

0/5 упр.

Представьте дом с несколькими комнатами. В каждой комнате свои вещи: лампа на тумбочке в спальне, нож в ящике на кухне. Вы можете пользоваться вещами в той комнате, где находитесь, и можете выйти в коридор, чтобы взять что-то общее. Но дотянуться до вещей в чужой закрытой комнате, не открыв дверь, не получится.

Переменные в Python устроены так же. Каждая переменная живёт в своей области видимости, то есть в определённой части кода, где эта переменная существует и доступна. Понимание областей видимости помогает писать аккуратные функции, которые случайно не перезаписывают данные друг друга.

В этом уроке вы узнаете о четырёх уровнях областей видимости в Python (правило LEGB), научитесь пользоваться ключевыми словами global и nonlocal и разберётесь, как замыкания позволяют внутренним функциям запоминать значения из родительской функции. К концу урока вы сможете уверенно разбираться даже с самыми запутанными ошибками вида «переменная не найдена».

Что такое локальная область видимости?

Переменная, созданная внутри функции, существует только внутри этой функции. Это называется локальной областью видимости. Представьте её как личную комнату: ничто за пределами функции не может её увидеть или тронуть.

Локальные переменные живут внутри функции
def greet():
message = "Hello from inside!"
print(message)
greet()
# This would cause an error:
# print(message) # NameError: name 'message' is not defined
Вывод
Hello from inside!

Переменная message создаётся внутри greet(), поэтому она локальна для этой функции. Как только greet() завершится, message исчезнет. Если попытаться использовать её снаружи, Python вызовет исключение NameError.

Каждый вызов функции создаёт новый набор локальных переменных. Между вызовами они не сохраняются:

Локальные переменные создаются заново при каждом вызове
def count_up():
x = 0
x = x + 1
print(f"x is {x}")
count_up() # x is 1
count_up() # x is 1 (starts fresh each time)
count_up() # x is 1
Вывод
x is 1
x is 1
x is 1

Что такое глобальная область видимости?

Переменная, созданная вне всех функций, живёт в глобальной области видимости. Это как вещь в коридоре вашего дома: её видно из любой комнаты.

Глобальные переменные видны внутри функций
language = "Python" # Global variable
def show_language():
print(f"Language: {language}")
def show_greeting():
print(f"Welcome to {language}!")
show_language()
show_greeting()
Вывод
Language: Python
Welcome to Python!

Обе функции могут читать глобальную переменную language. Но что произойдёт, если попытаться изменить её внутри функции?

Изменить глобальную переменную напрямую нельзя
counter = 0
def increment():
counter = counter + 1 # This causes an error!
print(counter)
# Uncomment the line below to see the error:
# increment()
Вывод
(нет вывода)

Этот код падает с ошибкой UnboundLocalError. Python видит внутри функции counter = ... и решает, что counter является локальной переменной. Но затем правая часть пытается прочитать counter раньше, чем ей присвоено локальное значение. Python запутывается.

Правило LEGB: как Python ищет переменные

Когда вы используете имя переменной, Python ищет его в четырёх местах в определённом порядке. Это называется правилом LEGB:

  • L - Local (локальная): переменные текущей функции.
  • E - Enclosing (объемлющая): переменные любой внешней (родительской) функции, если функции вложены.
  • G - Global (глобальная): переменные верхнего уровня модуля (файла).
  • B - Built-in (встроенная): встроенные имена Python, такие как print, len, range.
  • Python проверяет их по порядку: сначала L, потом E, потом G, потом B. Поиск останавливается на первом совпадении. Если имя не нашлось нигде, вы получите NameError.

    Правило LEGB в действии
    # B - Built-in: len is always available
    # G - Global scope
    x = "global x"
    def outer():
    # E - Enclosing scope
    x = "enclosing x"
    def inner():
    # L - Local scope
    x = "local x"
    print(x) # Finds "local x" first (L)
    inner()
    print(x) # Finds "enclosing x" (E is now L for outer)
    outer()
    print(x) # Finds "global x" (G)
    Вывод
    local x
    enclosing x
    global x

    Каждый print(x) находит свою переменную x, потому что ищет её, начиная со своей области видимости и двигаясь наружу. Внутренняя функция сразу находит свою локальную x и дальше не ищет.

    Ключевое слово global

    Если нужно изменить глобальную переменную внутри функции, об этом нужно явно сказать Python с помощью ключевого слова global. Это как получить особый ключ, чтобы выйти в коридор и переставить там вещи.

    Использование ключевого слова global
    score = 0
    def add_points(points):
    global score
    score = score + points
    print(f"Score is now {score}")
    add_points(10)
    add_points(25)
    add_points(5)
    print(f"Final score: {score}")
    Вывод
    Score is now 10
    Score is now 35
    Score is now 40
    Final score: 40

    Объявление global score говорит Python: «Я не создаю новую локальную переменную, я хочу использовать глобальную». Теперь присваивание score = score + points работает правильно.

    С global (сложнее отследить)
    total = 0
    
    def add(n):
        global total
        total = total + n
    
    add(5)
    add(3)
    print(total)  # 8
    С return (чище)
    def add(current, n):
        return current + n
    
    total = 0
    total = add(total, 5)
    total = add(total, 3)
    print(total)  # 8

    Объемлющая область видимости и замыкания

    Если определить функцию внутри другой функции, внутренняя функция получает доступ к переменным внешней. Область видимости внешней функции называется объемлющей (enclosing): это буква «E» в LEGB.

    Замыкание запоминает переменную из объемлющей функции
    def make_greeting(greeting):
    def greet(name):
    return f"{greeting}, {name}!"
    return greet
    hello = make_greeting("Hello")
    howdy = make_greeting("Howdy")
    print(hello("Alice"))
    print(hello("Bob"))
    print(howdy("Alice"))
    Вывод
    Hello, Alice!
    Hello, Bob!
    Howdy, Alice!

    Здесь происходит кое-что удивительное. make_greeting("Hello") завершает работу и возвращает внутреннюю функцию greet. Обычно локальная переменная greeting была бы уничтожена. Но она всё ещё нужна внутренней функции!

    Python сохраняет greeting, потому что greet на неё ссылается. Такое сочетание функции и переменных, которые она запомнила из объемлющей области видимости, называется замыканием (closure).

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

    Замыкание как фабрика функций
    def make_multiplier(factor):
    def multiply(number):
    return number * factor
    return multiply
    double = make_multiplier(2)
    triple = make_multiplier(3)
    print(double(5)) # 10
    print(triple(5)) # 15
    print(double(100)) # 200
    Вывод
    10
    15
    200

    Ключевое слово nonlocal

    Как global позволяет изменять глобальные переменные, так nonlocal позволяет изменять переменные объемлющей области видимости. Без него присваивание создало бы новую локальную переменную.

    nonlocal для изменения переменных объемлющей функции
    def make_counter():
    count = 0
    def increment():
    nonlocal count
    count = count + 1
    return count
    return increment
    counter = make_counter()
    print(counter()) # 1
    print(counter()) # 2
    print(counter()) # 3
    Вывод
    1
    2
    3

    Без nonlocal count строка count = count + 1 упала бы с ошибкой UnboundLocalError, той же, что мы видели с глобальными переменными. Ключевое слово nonlocal говорит Python искать переменную в ближайшей внешней области видимости.

    Это мощный приём. Переменная count полностью скрыта от внешнего мира. Никакой код за пределами make_counter не может прочитать или испортить её. Это одна из форм инкапсуляции, то есть сокрытия данных.

    Частые ошибки с областями видимости и как их исправить

    Вот ошибки с областями видимости, на которых Python-разработчики спотыкаются чаще всего. Если знать эти шаблоны, можно сэкономить часы отладки.

    Ошибка 1: затенение глобальной переменной

    Затенение: локальная переменная скрывает глобальную
    items = ["apple", "banana", "cherry"]
    def process():
    items = [] # Creates a NEW local variable, doesn't touch the global!
    items.append("date")
    print(f"Inside: {items}")
    process()
    print(f"Outside: {items}") # Global is unchanged!
    Вывод
    Inside: ['date']
    Outside: ['apple', 'banana', 'cherry']

    items = [] внутри функции создаёт совершенно новую локальную переменную, которая затеняет (скрывает) глобальную. Глобальный список items при этом не меняется. Обычно это ошибка.

    Ошибка 2: UnboundLocalError из-за смешения чтения и записи

    Разбираем UnboundLocalError
    total = 100
    def update():
    # Python sees "total = ..." below and marks total as local
    # But this line tries to READ it before it's assigned locally
    print(total) # UnboundLocalError!
    total = 200
    # Uncomment to see the error:
    # update()
    Вывод
    (нет вывода)

    Python просматривает всё тело функции ещё до её запуска. Если он видит хоть одно присваивание total, он помечает total как локальную переменную для всей функции. Тогда строка print(total) пытается прочитать локальную переменную, которой ещё ничего не присвоено.


    Практические упражнения

    Пора применить области видимости и замыкания на практике. Упражнения идут от чтения кода с разными областями видимости к созданию собственных замыканий.

    Упражнение 1: предскажите вывод (области видимости)
    Угадайте вывод

    Внимательно прочитайте код и предскажите, что он выведет. Затем напишите одну инструкцию print(), которая выводит точно такой же результат.

    x = 10
    
    def foo():
        x = 20
        print(x)
    
    foo()
    print(x)

    Выведите два числа на отдельных строках, точно так, как их вывел бы код выше.

    # Print the exact output of the code above
    Упражнение 2: исправьте ошибку в глобальном счётчике
    Исправьте ошибку

    Этот код пытается увеличить глобальный счётчик, но падает с ошибкой UnboundLocalError. Исправьте его, чтобы вывод был таким:

    1
    2
    3
    count = 0
    def increment():
    count = count + 1
    print(count)
    increment()
    increment()
    increment()
    Упражнение 3: замыкание для приветствий
    Напишите код

    Напишите функцию make_greeter, которая принимает строку greeting как аргумент и возвращает новую функцию. Возвращённая функция должна принимать аргумент name и выводить приветствие с этим именем.

    Например:

    hello = make_greeter("Hello")
    hello("World")

    Должно вывести: Hello, World!

    Тест вызовет:

    hi = make_greeter("Hi")
    hi("Alice")
    hi("Bob")

    Ожидаемый вывод:

    Hi, Alice!
    Hi, Bob!
    def make_greeter(greeting):
    # Return a function that prints greeting + name
    pass
    hi = make_greeter("Hi")
    hi("Alice")
    hi("Bob")
    Упражнение 4: счётчик с nonlocal
    Напишите код

    Напишите функцию make_counter, которая принимает начальное значение start и возвращает функцию. При каждом вызове возвращённая функция должна выводить текущее значение счётчика, а затем увеличивать его на 1.

    Тест вызовет:

    c = make_counter(1)
    c()
    c()
    c()

    Ожидаемый вывод:

    1
    2
    3
    def make_counter(start):
    # Your code here
    pass
    c = make_counter(1)
    c()
    c()
    c()
    Упражнение 5: замыкание, которое считает текущее среднее
    Напишите код

    Напишите функцию make_averager, которая возвращает функцию. При каждом вызове с числом возвращённая функция должна выводить текущее среднее всех чисел, переданных ей до сих пор.

    Текущее среднее равно сумме всех чисел, делённой на их количество.

    Тест вызовет:

    avg = make_averager()
    avg(10)
    avg(20)
    avg(30)

    Ожидаемый вывод:

    10.0
    15.0
    20.0

    Подсказка: функции нужно помнить и общую сумму, и количество чисел.

    def make_averager():
    # Your code here
    pass
    avg = make_averager()
    avg(10)
    avg(20)
    avg(30)

    Итоги

    Вот что вы узнали об областях видимости в Python:

    ПонятиеЧто это значит
    Локальная область видимостиПеременные, созданные внутри функции, существуют только там
    Глобальная область видимостиПеременные, созданные на уровне модуля, видны везде
    Правило LEGBPython ищет имена в порядке Local, Enclosing, Global, Built-in
    Ключевое слово globalПозволяет функции изменять переменную уровня модуля
    Объемлющая область видимостиПеременные внешней функции, доступные внутренним функциям
    ЗамыканиеФункция, которая запоминает переменные из объемлющей области видимости
    Ключевое слово nonlocalПозволяет внутренней функции изменять переменную объемлющей функции

    Золотое правило: лучше передавать аргументы и возвращать значения, чем использовать global или nonlocal. Исключение составляют замыкания: здесь объемлющая область видимости показывает себя во всей красе и позволяет создавать специализированные функции с собственным скрытым состоянием.

    Что дальше?

    Теперь, когда вы разобрались с областями видимости и замыканиями, можно переходить к уроку [Рекурсия](/python/python-recursion), где функции вызывают сами себя. Для рекурсии важно понимать области видимости, потому что каждый рекурсивный вызов получает собственную локальную область видимости.

    Похожие уроки

    Урок был полезен?