Скидка до 60% и курс по ИИ в подарок 3 дня 09 :07 :03 Выбрать курс
Код
#статьи

Как работать с глобальными переменными в Python

База, которую нужно знать для безопасной работы.

Иллюстрация: Оля Ежак для Skillbox Media

Переменные в Python — одна из первых тем, с которой сталкиваются начинающие разработчики. На первый взгляд всё просто: создал переменную и работай. Но на практике оказывается, что они бывают разных видов. Особенно много проблем возникает с глобальными переменными, и именно они часто приводят к ошибкам.

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

Содержание


Что такое глобальные переменные и в чём их опасность

Глобальная переменная — это переменная, которая объявлена вне функции или класса. Её можно читать из любого места программы, но для изменения значения внутри функции нужно использовать специальное ключевое слово. Для начала давайте посмотрим пример.

message = "Привет, мир!"  

def greet():
    print(message)

greet()  # Привет, мир!

Переменная message объявлена глобально — вне функции. Поэтому функция greet () может прочитать её значение и вывести на экран.

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

def greet():
    message = "Привет, мир!"
    print("Внутри функции:", message)  # Внутри функции: Привет, мир!

greet()

# После выполнения кода message больше не существует
print("Снаружи функции:", message)  # Ошибка: NameError: name 'message' is not defined

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

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

def show_count():
    print(count)      # Пытаемся вывести значение до присваивания
    count = 20        # После этой строки count становится локальной переменной

show_count()  # UnboundLocalError: cannot access local variable 'count' where it is not associated with a value

Интерпретатор Python анализирует весь код функции перед её выполнением. Когда он видит присваивание count = 20, то помечает count как локальную переменную на протяжении всей функции.

Поэтому при попытке вывести print (count) перед присваиванием Python обращается к локальной переменной count, которая ещё не инициализирована. В результате возникает ошибка UnboundLocalError.

Как глобальные переменные могут нарушать логику программы

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

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

Например, в коде ниже обе функции используют глобальную переменную processing_mode. Функция main () задаёт ей значение 1 и ожидает стандартного режима работы программы. Однако вызов process_data () меняет это значение на 2. В итоге условие в main () выполняется неверно, поскольку состояние программы стало другим.

# Глобальная настройка режима обработки
processing_mode = 0  # 0 — не установлен, 1 — стандарт, 2 — спецрежим

def process_data():
    global processing_mode
    processing_mode = 2  # Меняем режим
    print("Данные обработаны")

def main():
    global processing_mode
    processing_mode = 1  # Ожидаем стандартный режим для остального кода

    process_data()       # Во время вызова режим станет 2 

    # Логика опирается на состояние, которое мы изменили
    if processing_mode == 1:
        print("Стандартная обработка")
    else:
        print("Режим изменён unexpectedly")

main()  # Режим изменён unexpectedly

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

Однако при работе с константами проблемы со скрытыми зависимостями нет. Например, константы вроде MAX_CONNECTIONS = 100 (максимальное количество подключений к серверу) или API_URL = «<https://api.example.com&gt; (адрес внешнего API) задаются один раз при запуске программы и не меняются в процессе её работы. Их принято записывать заглавными буквами и выносить в отдельный модуль.

Как работать с переменными в разных областях видимости

Чтобы избежать конфликтов имён, необходимо понимать области видимости переменных — правила, определяющие, где переменная доступна для чтения и изменения. Главный принцип: переменная доступна там, где она создана, и во всех вложенных блоках кода.

Вернёмся к примеру с ошибкой UnboundLocalError и исправим её. Чтобы изменить глобальную переменную внутри функции, нужно использовать перед её именем ключевое слово global. Это явно указывает Python, что мы работаем с глобальной переменной, а не создаём новую локальную. Давайте посмотрим на примере.

count = 10  

def show_count():
    global count  # Указываем, что хотим изменить глобальную переменную
    count = 20    # Меняем глобальную переменную
    print("Внутри функции:", count)

show_count()       # Внутри функции: 20
print("Снаружи функции:", count)  # Снаружи функции: 20

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

count = 100 

def outer():
    count = 10  # Переменная внешней функции 

    def inner():
        nonlocal count  # Меняем переменную из outer()
        count += 5
        print("Внутри inner:", count)  # Внутри inner: 15

    inner()
    print("После inner в outer:", count)  # После inner в outer: 15

outer()
print("Снаружи функции:", count)  # Снаружи функции: 100

Когда Python встречает переменную, он ищет её по строгому алгоритму — правилу LEGB. Это обозначение уровней областей видимости:

  • Local — локальная область внутри функции.
  • Enclosing — область внешней функции (для вложенных функций).
  • Global — глобальная область видимости модуля.
  • Built-in — встроенная область Python с предопределёнными функциями и исключениями (len (), print (), str () и другие).

Python проверяет эти области последовательно: сначала локальную, затем внешнюю, потом глобальную и, наконец, встроенную.

Области видимости переменных в Python: интерпретатор начинает поиск из центра и движется к внешним кругам, пока не найдёт нужное имя
Инфографика: Майя Мальгина для Skillbox Media

Как управлять изменяемыми объектами и переприсваиванием

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

config = {"debug": False}  # Глобальный словарь

def enable_debug():
    # global не нужен для изменения содержимого
    config["debug"] = True
    config["mode"] = "verbose"
    print("Debug включён")

enable_debug()
print(config)  # {'debug': True, 'mode': 'verbose'}

Но если бы мы попытались присвоить переменной config новый словарь, здесь уже понадобилось бы ключевое слово global.

config = {"debug": False}

def reset_config():
    global config  # Теперь global нужен
    config = {}  # Полное переприсваивание
    print("Конфиг сброшен")

reset_config()
print(config)  # {}

Как организовать взаимодействие с глобальными переменными между модулями

В реальных проектах код разбивается на модули, поэтому часто необходимо использовать одну глобальную переменную в разных файлах. Самый простой способ — создать отдельный модуль для хранения глобальных переменных. Назовём его settings.py.

# settings.py
APP_NAME = "MyApp"
DEBUG_MODE = False
max_connections = 100

Теперь эти переменные можно импортировать в другие модули с помощью оператора import и обращаться к ним через имя модуля.

# main.py
import settings

print(settings.APP_NAME)  # MyApp
print(settings.max_connections)  # 100

Чтобы изменить глобальную переменную из другого модуля, обращайтесь к ней через имя модуля.

# main.py
import settings  

def toggle_debug():
    settings.DEBUG_MODE = not settings.DEBUG_MODE
    print(f"Debug mode: {settings.DEBUG_MODE}")

toggle_debug()  # Debug mode: True

Если вы импортируете переменную напрямую через конструкцию from settings import DEBUG_MODE, то получите копию её значения, а не ссылку на переменную в модуле settings. Это значит, что любые изменения этой переменной в текущем модуле создадут локальную переменную и не повлияют на исходное значение в settings.py.

# Неправильный способ для изменяемых значений
from settings import DEBUG_MODE # Импортируется копия значения, а не сама переменная

DEBUG_MODE = True  # Создаёт новую локальную переменную, не изменяя settings.DEBUG_MODE

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

# settings.py
users = ["admin", "guest"]  # Глобальный список пользователей

# main.py
from settings import users  # Импортируем объект списка

users.append("editor")  # Добавляем элемент, не переприсваивая переменную
print(users)  # ['admin', 'guest', 'editor']

В примере выше глобальная переменная users определена как список — изменяемый объект. При импорте через from settings import users Python передаёт только ссылку на объект, а не копию данных. Поэтому users.append («editor») изменяет содержимое списка в модуле settings, но не создаёт при этом новую переменную. Однако если бы произошло переприсваивание (например, users = [«editor»]), то Python создал бы новую локальную переменную, не затронув исходный список.

Ещё один подход — использовать функции для доступа к глобальным переменным. Такие переменные часто называют «приватными» и обозначают подчёркиванием в начале имени — например, _debug_mode.

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

# settings.py
_debug_mode = False  # Внутренняя переменная 

def is_debug():
    # Возвращает текущее значение глобальной переменной
    return _debug_mode

def set_debug(value):
    # Меняет значение глобальной переменной 
    global _debug_mode
    _debug_mode = value


# main.py
import settings  # Модуль с настройками 

settings.set_debug(True)       # Меняем значение через функцию
print(settings.is_debug())     # Проверяем состояние: True

Переменная _debug_mode хранится в модуле settings.py, а функции is_debug () и set_debug () обеспечивают к ней контролируемый доступ. Этот подход защищает глобальные данные от случайных изменений и упрощает отладку: вся логика обновления проходит через одну точку.

Например, чтобы добавить логирование или проверку при изменении _debug_mode, нужно поправить функцию set_debug () — не придётся искать все места, где переменная могла быть изменена напрямую.

Общие рекомендации по работе с глобальными переменными

Хотя глобальные переменные могут создавать проблемы, иногда без них не обойтись. Например, они полезны для хранения настроек или общих ресурсов. Если вам всё же нужно их использовать, соблюдайте несколько правил — они помогут избежать распространённых ошибок.

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

g_counter = 0  
g_user_session = None

def increment():
    global g_counter
    g_counter += 1
    print(f"Счётчик: {g_counter}")

increment()  # Счётчик: 1

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

# Было: функция обращается к глобальной переменной напрямую
g_config = {"debug": True, "verbose": False}

def process_data():
    if g_config["debug"]:  # Зависимость от внешней переменной
        print("Debug mode")
    return "data"

# Стало: конфигурация передаётся явно через параметр
def process_data(config):  # Теперь видно, от каких данных зависит функция
    if config["debug"]:
        print("Debug mode")
    return "data"

# Передаём конфигурацию при вызове и можем тестировать функцию с разными параметрами
result = process_data(g_config)

Используйте как можно меньше изменяемых глобальных переменных. Старайтесь применять глобальные переменные для констант, параметров конфигурации, единственного экземпляра объекта, счётчиков и флагов — когда другие решения избыточны.

Для остального рассмотрите альтернативные подходы: передавайте параметры через функции, применяйте классы или контекстные менеджеры. Например, вместо глобального счётчика запросов, который может изменяться из разных частей программы, используйте класс.

# Плохо: глобальная изменяемая переменная
request_count = 0

def handle_request():
    global request_count
    request_count += 1
    print(f"Запросов: {request_count}")

# Хорошо: инкапсуляция состояния в класс
class RequestCounter:
    def __init__(self):
        self.count = 0
    
    def increment(self):
        self.count += 1
        print(f"Запросов: {self.count}")

counter = RequestCounter()
counter.increment()  # Запросов: 1

Вместо глобальной переменной мы создаём класс RequestCounter, который инкапсулирует состояние счётчика и обеспечивает контролируемый доступ через метод increment (). Это позволяет каждой части программы работать со своим счётчиком — данные изолированы, и разные модули не могут непредсказуемо изменять общее состояние.

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

Больше интересного про код — в нашем телеграм-канале.  Подписывайтесь!



Листая дальше, вы перейдете на страницу Python для всех: старт в IT с нуля



Бесплатный курс по разработке на Python ➞
Пройдите бесплатный курс по Python и создайте с нуля телеграм-бот, веб-парсер и сайт. Спикер — руководитель отдела разработки в «Сбере».
Пройти курс
Понравилась статья?
Да

Пользуясь нашим сайтом, вы соглашаетесь с тем, что мы используем cookies 🍪

Ссылка скопирована