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

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

Правило LEGB

Для определения, где Python ищет имя переменной или другого объекта, используется правило LEGB. Это аббревиатура определяет порядок поиска имен. Python ищет имя, начиная с самой внутренней (локальной) области и двигаясь наружу. Если имя найдено, поиск прекращается. Если оно не найдено ни в одной из областей, возникает исключение NameError.

Локальная область (L от англ. Local)

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

message = "Желаю необычайно хорошего настроения!"


def show_message() -> None:
    message = "Пусть день начнется с позитива! Пройдет легко и без надрыва!"
    print(message)


show_message()
# Вывод: Пусть день начнется с позитива! Пройдет легко и без надрыва!
print(message)
# Вывод: Желаю необычайно хорошего настроения!

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

Нелокальная область (E от англ. Enclosing)

Если одна функция содержит другую, то локальные переменные внешней функции будут нелокальными переменными вложенной функции:

def greet() -> None:
    greeting = "Приветствую! Чудесного, доброго и солнечного дня тебе!"
    
    def smile() -> None:
        print(greeting, end=" ")
        emoji = ":)"
        print(emoji)
        
    smile()


greet()
# Вывод: Приветствую! Чудесного, доброго и солнечного дня тебе! :)

Здесь функция smile() вызывается внутри функции greet(), однако она не имеет собственной переменной greeting, поэтому эта вложенная функция ищет её в нелокальной области видимости – теле внешней функции greet().

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

Глобальная область (G от англ. Global)

Это все имена, определенные в файле с кодом, но не внутри какой-либо функции. Переменные, созданные в глобальной области, называются глобальными переменными, и доступны для чтения и изменения из любой части файла, в котором они были объявлены.

Например, в нашем файле с кодом у нас есть глобальная переменная goodbye:

goodbye = "До встречи, берегите себя!"

Мы можем получить доступ к ней из любой функции в этом файле:

def say_goodbye() -> None:
    print(goodbye)


say_goodbye()
# Вывод: До встречи, берегите себя!

Встроенная область (B от англ. Built-in)

Это самая внешняя область видимости, которая включает все встроенные в Python имена, такие как print, len, sum, str и другие. Они доступны в любой части программы.

Глобальные и локальные переменные

Мы уже упоминали о локальных и глобальных переменных, но давайте рассмотрим их более подробно.

Локальные переменные – это переменные, созданные внутри функции. Они существуют только в течение выполнения этой функции и доступны только внутри неё. Как только выполнение функции завершается, локальные переменные уничтожаются, и доступ к ним извне становится невозможен.

Глобальные переменные – это переменные, созданные на верхнем уровне программы, то есть вне каких-либо функций или классов. Они доступны для чтения и использования из любой части кода в этом файле, включая любые функции.

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

Чтобы явно указать Python, что вы хотите изменить именно глобальную переменную, а не создать новую локальную, используется ключевое слово global перед именем переменной:

number = 10  # Глобальная переменная


def add_two() -> None:
    # Используем глобальную переменную, а не создаём локальную
    global number  
    number = number + 2
    print(f"Внутри функции number = {number}")


add_two()
# Вывод: Внутри функции number = 12
print(f"Снаружи функции number = {number}")
# Вывод: Снаружи функции number = 12

Конструкция global number позволяет функции add_two() изменить значение глобальной переменной number.

Локальные и нелокальные переменные

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

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

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

def modify_number() -> None:
    number = 10
 
    def split_in_half() -> None:
        nonlocal number
        number /= 2
    
    split_in_half()
    print(number)

    
modify_number()
# Вывод: 5.0

Конструкция nonlocal number позволяет вложенной функции split_in_half() изменить значение переменной number из внешней функции modify_number().

Примеры

Пример 1. Учёт остатков на складе

Функция ship_product() осуществляет отправку товара со склада с учётом общего количества товаров, хранящегося в глобальной переменной total_stock:

total_stock = 500  # Общий запас товара (глобальная переменная)


def ship_product(quantity_shipped: int) -> None:
    """Отправляет товар со склада, уменьшая общее количество.
    
    Параметры:
        quantity_shipped: Количество товара для отправки. 
    """
    global total_stock
    
    if quantity_shipped <= total_stock:
        total_stock -= quantity_shipped
        print(f"Отправлено: {quantity_shipped} единиц. Остаток на складе: {total_stock} единиц")
    else:
        print("Недостаточно товара на складе для отправки")


# Показываем текущий запас
print(f"Текущий запас на складе: {total_stock} единиц")

# Отправляем товары и наблюдаем за изменением глобальной переменной
ship_product(150)
ship_product(300)

# Показываем итоговый остаток
print(f"Итоговый остаток на складе: {total_stock} единиц")

Вывод:

Текущий запас на складе: 500 единиц
Отправлено: 150 единиц. Остаток на складе: 350 единиц
Отправлено: 300 единиц. Остаток на складе: 50 единиц
Итоговый остаток на складе: 50 единиц

Пример 2. Ипотечный калькулятор

Калькулятор рассчитывает ежемесячный платеж по ипотеке в зависи-мости от суммы кредита. Процентная ставка loan_rate может изменяться, но для удобства она определена во внешней функции calculate_mortgage(), а расчёты проводятся во вложенной функции calculate_monthly_payment():

def calculate_mortgage(loan_amount: float) -> None:
    """Рассчитывает ежемесячный платеж по ипотеке.
    
    Параметры:
        loan_amount: Сумма кредита.
    """
    loan_rate = 0.07 # Переменная внешней функции (нелокальная для вложенной)
    
    def calculate_monthly_payment(years: int) -> None:
        """Вложенная функция, которая рассчитывает платеж
        на основе процентной ставки внешней функции.
        
        Параметры:
            years: Количество лет, на которое берётся кредит.
        """
        months = years * 12
        monthly_rate = loan_rate / 12
        payment = (loan_amount * monthly_rate) / (1 - (1 + monthly_rate) ** (-months))
        print(f"Ежемесячный платеж на {years} лет: {payment:.2f} руб.")
    
    # Рассчитываем ежемесячный платёж для 15 и 20 лет
    calculate_monthly_payment(15)
    calculate_monthly_payment(20)


# Запускаем калькулятор для суммы 5,000,000
calculate_mortgage(5_000_000)

Вывод:

Ежемесячный платеж на 15 лет: 44941.41 руб.
Ежемесячный платеж на 20 лет: 38764.95 руб.

Пример 3. Управление очками в игре

Функция start_game() симулирует запуск игры, начисление очков в ко-торой осуществляется с помощью вложенной функции add_points(). Эта функция увеличивает значение переменной внешней функции score на пере-данное количество очков points. Для этого она использует ключевое слово nonlocal, которое позволяет функции изменять переменную score из внешней области видимости, а не создавать новую локальную переменную с этим же именем:

def start_game() -> None:
    """Запускает игру и управляет счётом очков игрока.
    """
    score = 0  # Локальная переменная внешней функции (нелокальная для вложенной)
    
    def add_points(points: int) -> None:
        """Вложенная функция, которая добавляет очки к счету игрока.
        
        Параметры:
            points: Количество очков, которые заработал игрок.
        """
        nonlocal score
        score += points
        print(f"Добавлено {points} очков. Текущий счет: {score}")
    
    # Симуляция игровых событий
    print(f"Начало игры. Счет: {score}")
    add_points(10)  # Добавляем 10 очков
    add_points(25)  # Добавляем 25 очков
    add_points(5)  # Добавляем 5 очков
    print(f"Конец игры. Итоговый счет: {score}")


# Запускаем игру
start_game()

Вывод:

Начало игры. Счет: 0
Добавлено 10 очков. Текущий счет: 10
Добавлено 25 очков. Текущий счет: 35
Добавлено 5 очков. Текущий счет: 40 
Конец игры. Итоговый счет: 40

Итоги

  • Область видимости переменной определяет, в какой части программы можно получить доступ к этой переменной.
  • Пространства имён – это система, сопоставляющая каждому имени соответствующий объект.
  • Правило LEGB определяет, где Python ищет имя объекта.
  • Локальная область видимости – это область видимости внутри функции, которая включает в себя имена, определённые внутри неё.
  • Нелокальная область видимости – это область видимости внешней функции для вложенной в неё другой функции.
  • Глобальная область видимости – это область видимости всего файла с кодом.
  • Встроенная область видимости – это область видимости, которая включает в себя все встроенные в Python имена.
  • Локальные переменные – это переменные, созданные внутри функции.
  • Глобальные переменные – это переменные, созданные вне каких-либо функций, и доступные для чтения и изменения из любой части файла с кодом.
  • Нелокальные переменные – это локальные переменные внешней функции для вложенной функции.
  • По умолчанию, функции могут только читать переменные из глобальной и нелокальной областей, но не изменять их.
  • Ключевое слово global позволяет функции изменять значение глобальной переменной.
  • Ключевое слово nonlocal позволяет внутренней функции изменять значение переменной внешней функции.

Задания для самопроверки

1. Что такое область видимости переменной и пространство имён в Python?

Область видимости переменной – это часть программы, в которой перемен-ная доступа для использования.  Пространства имён – это система, сопостав-ляющая каждому имени соответствующий объект.

2. Объясните, как работает правило LEGB при поиске переменной.

Правило LEGB определяет, где Python ищет имя объекта. Поиск начинается с локальной (L) области (внутри текущей функции). Если имя не найдено, поиск переходит в нелокальную (E) область (для переменных во внешней функции). Далее идет глобальная (G) область (уровень модуля), и, наконец, встроенная (B) область, содержащая стандартные функции Python.

3. Что делают ключевые слова global и nonlocal? В каких случаях их нужно использовать?

Ключевое слово global позволяет функции изменять значение глобальной переменной, а слово nonlocal позволяет внутренней функции изменять значение переменной внешней функции (нелокальной переменной).

4. Что будет выведено на экран в результате выполнения следующего кода? Объясните свой ответ.

name = "Николай"


def change_name() -> None:
    name = "Павел"


change_name()
print(f"Император России: {name} Романов")

Будет выведена строка "Император России: Николай Романов", так как функция change_name() не изменяет значение глобальной переменной name, а создаёт новую локальную переменную name.

5. Создайте глобальную переменную total_sum = 0. Напишите функцию add_to_total(number), которая принимает одно число number и добавляет его к total_sum. Вызовите эту функцию для number = 10, а затем для number = 5, после чего выведите на экран значение переменной total_sum.

total_sum = 0 


def add_to_total(number: int) -> None:
    global total_sum 
    total_sum += number


add_to_total(10)
add_to_total(5)
print(total_sum)
# Вывод: 15