warnings — Управление предупреждениями


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

Программисты Python выдают предупреждения, вызывая функцию warn(), определенную в данном модуле. (Программисты на C используют PyErr_WarnEx(); подробности см. в Обработка исключений).

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

Управление предупреждениями состоит из двух этапов: во-первых, каждый раз, когда выдается предупреждение, определяется, следует ли выдавать сообщение; затем, если сообщение должно быть выпущено, оно форматируется и печатается с использованием настраиваемой пользователем хука (hook).

Решение о том, выдавать ли предупреждающее сообщение, контролируется фильтром предупреждений, который представляет собой последовательность правил и действий сопоставления. Правила можно добавить в фильтр, вызвав filterwarnings(), и сбросить до состояния по умолчанию, вызвав resetwarnings().

Распечатка предупреждающих сообщений выполняется путём вызова showwarning(), которую можно отменить; реализация по умолчанию данной функции форматирует сообщение, вызывая formatwarning(), доступную для использования в пользовательских реализациях.

См.также

logging.captureWarnings() позволяет обрабатывать все предупреждения с помощью стандартной инфраструктуры логирования.

Категории предупреждений

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

Хотя технически это встроенные исключения, они задокументированы здесь, поскольку концептуально относятся к механизму предупреждений.

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

В настоящее время определены следующие классы категорий предупреждений:

Класс Описание
Warning Базовый класс всех классов категории предупреждения. Подкласс Exception.
UserWarning Категория по умолчанию для warn().
DeprecationWarning Базовая категория для предупреждений об устаревших функциях, когда эти предупреждения предназначены для других разработчиков Python (игнорируются по умолчанию, если не вызвано кодом в __main__).
SyntaxWarning Базовая категория для предупреждений о сомнительных синтаксических особенностях.
RuntimeWarning Базовая категория для предупреждений о сомнительных функциях среды выполнения.
FutureWarning Базовая категория для предупреждений об устаревших функциях, если эти предупреждения предназначены для конечных пользователей приложений, написанных в Python.
PendingDeprecationWarning Базовая категория для предупреждений о функциях, которые будут устаревшими в будущем (игнорируется по умолчанию).
ImportWarning Базовая категория для предупреждений, запускаемых в процессе импорта модуля (игнорируется по умолчанию).
UnicodeWarning Базовая категория для предупреждений, связанных с Юникодом.
BytesWarning Базовая категория для предупреждений, связанных с bytes и bytearray.
ResourceWarning Базовая категория для предупреждений, связанных с использованием ресурсов.

Изменено в версии 3.7: Ранее DeprecationWarning и FutureWarning различались в зависимости от того, удалялась ли функция полностью или изменялось ли её поведение. Теперь они различаются по целевой аудитории и способу обработки с помощью фильтров предупреждений по умолчанию.

Фильтр предупреждений

Фильтр предупреждений определяет, будут ли предупреждения игнорироваться, отображаться или превращаться в ошибки (вызывая исключение).

По сути, фильтр предупреждений поддерживает упорядоченный список спецификаций фильтра; любое предупреждение сопоставляется с каждой спецификацией фильтра в списке по очереди, пока не будет найдено совпадение; фильтр определяет расположение совпадения. Каждая запись представляет собой кортеж вида (action, message, category, module, lineno), где:

  • action — одна из следующих строк:

    Значение Расположение
    "default" распечатать первое появление соответствующих предупреждений для каждого местоположения (модуль + номер строки), где выдается предупреждение
    "error" превратить соответствующие предупреждения в исключения
    "ignore" никогда не печатать соответствующие предупреждения
    "always" всегда печатать соответствующие предупреждения
    "module" распечатать первое появление соответствующих предупреждений для каждого модуля, в котором выдается предупреждение (независимо от номера строки)
    "once" напечатать только первое появление соответствующих предупреждений, независимо от местоположения
  • message — строка, содержащая регулярное выражение, которому должно соответствовать начало предупреждающего сообщения. Выражение всегда учитывает регистр.

  • category — класс (подкласс Warning), для которого категория предупреждения должна быть подклассом.

  • module — строка, содержащая регулярное выражение, которому должно соответствовать имя модуля. Выражение составлено с учётом регистра.

  • lineno — целое число, определяющее номер строки, в которой возникло предупреждение, или 0, чтобы соответствовать всем номерам строк.

Поскольку класс Warning является производным от встроенного класса Exception, чтобы превратить предупреждение в ошибку, мы просто поднимаем category(message).

Если выдаётся предупреждение и не соответствует ни одному зарегистрированному фильтру, применяется действие «по умолчанию» (отсюда и его название).

Описание фильтров предупреждений

Фильтр предупреждений инициализируется параметрами -W, переданными в командную строку интерпретатора Python и переменной среды PYTHONWARNINGS. Интерпретатор сохраняет аргументы для всех предоставленных записей без интерпретации в sys.warnoptions; модуль warnings анализирует их при первом импорте (недопустимые параметры игнорируются после вывода сообщения в sys.stderr).

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

action:message:category:module:line

Значение каждого из этих полей описано в Фильтр предупреждений. При перечислении нескольких фильтров в одной строке (как для PYTHONWARNINGS) отдельные фильтры разделяются запятыми, а фильтры перечисленные позже, приоритетнее тех, которые перечислены перед ними (поскольку они применяются слева направо и последние применённые фильтры приоритетнее более ранних).

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

default                      # Показать все предупреждения (даже те, которые по умолчанию игнорируются)
ignore                       # Игнорировать все предупреждения
error                        # Преобразовать все предупреждения в ошибки
error::ResourceWarning       # Считать сообщения ResourceWarning ошибками
default::DeprecationWarning  # Показать сообщения DeprecationWarning
ignore,default:::mymodule    # Сообщать только о предупреждениях, вызванных «mymodule»
error:::mymodule[.*]         # Преобразование предупреждений в ошибки в «mymodule»
                             # и любых подпакетов «mymodule»

Фильтр предупреждений по умолчанию

По умолчанию Python устанавливает несколько фильтров предупреждений, которые можно переопределить параметром командной строки -W, переменной среды PYTHONWARNINGS и вызовами filterwarnings().

В сборках обычного релиза у фильтра предупреждений по умолчанию следующие записи (в порядке приоритета):

default::DeprecationWarning:__main__
ignore::DeprecationWarning
ignore::PendingDeprecationWarning
ignore::ImportWarning
ignore::ResourceWarning

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

Изменено в версии 3.2: DeprecationWarning теперь по умолчанию игнорируется в дополнение к PendingDeprecationWarning.

Изменено в версии 3.7: DeprecationWarning снова отображается по умолчанию, когда запускается непосредственно кодом в __main__.

Изменено в версии 3.7: BytesWarning больше не отображается в списке фильтров по умолчанию и вместо этого настраивается через sys.warnoptions, когда -b указывается дважды.

Отмена фильтра по умолчанию

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

import sys

if not sys.warnoptions:
    import warnings
    warnings.simplefilter("ignore")

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

import sys

if not sys.warnoptions:
    import os, warnings
    warnings.simplefilter("default") # Изменить фильтр в этом процессе
    os.environ["PYTHONWARNINGS"] = "default" # Также влияет на подпроцессы

Наконец, разработчикам интерактивных оболочек, которые запускают пользовательский код в пространстве имён, отличном от __main__, рекомендуется обеспечить отображение сообщений DeprecationWarning по умолчанию, используя следующий код (где user_ns — модуль, используемый для выполнения кода, набранного в интерактивном режиме):

import warnings
warnings.filterwarnings("default", category=DeprecationWarning,
                                   module=user_ns.get("__name__"))

Временное подавление предупреждений

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

import warnings

def fxn():
    warnings.warn("deprecated", DeprecationWarning)

with warnings.catch_warnings():
    warnings.simplefilter("ignore")
    fxn()

В менеджере контекста все предупреждения просто игнорируются. Это позволяет использовать заведомо устаревший код без необходимости видеть предупреждение, не подавляя при этом предупреждение для другого кода, который может не знать об использовании устаревшего кода. Примечание: гарантируется только в однопоточном приложении. Если два или более потока используют менеджер контекста catch_warnings одновременно, поведение не определено.

Предупреждения при тестировании

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

import warnings

def fxn():
    warnings.warn("deprecated", DeprecationWarning)

with warnings.catch_warnings(record=True) as w:
    # Всегда запускать все предупреждения.
    warnings.simplefilter("always")
    # Запустить предупреждение.
    fxn()
    # Проверить некоторые вещи
    assert len(w) == 1
    assert issubclass(w[-1].category, DeprecationWarning)
    assert "deprecated" in str(w[-1].message)

Можно также сделать все предупреждения исключениями, используя error вместо always. Следует помнить, что если предупреждение уже было выдано из-за правила once/default, то независимо от того, какие фильтры установлены, предупреждение не будет отображаться снова, если реестр предупреждений, связанный с предупреждением, не будет очищен.

После выхода из менеджера контекста фильтр предупреждений восстанавливается до состояния, в котором был вход в контекст. Это предотвращает непредвиденное изменение фильтра предупреждений в тестах между тестами и получение неопределённых результатов. Функция showwarning() в модуле также восстанавливается до исходного значения. Примечание: гарантируется только в однопоточном приложении. Если два или более потока одновременно используют менеджер контекста catch_warnings, поведение не определено.

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

Обновление кода для новых версий зависимостей

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

Примечательно, что этот список «игнорируется по умолчанию» включает DeprecationWarning (для каждого модуля, кроме __main__), что означает, что разработчики должны обязательно тестировать свой код с обычно игнорируемыми предупреждениями, сделанными видимыми, чтобы своевременно получать уведомления о будущих критических изменениях API (будь то в стандартной библиотеке или сторонних пакетах).

В идеальном случае у кода будет подходящее множество тестов, и средство выполнения тестов позаботится о неявном включении всех предупреждений при запуске тестов (это делает средство запуска тестов, предоставляемое модулем unittest).

В менее идеальных случаях приложения можно проверить на использование устаревших интерфейсов, передав -Wd интерпретатору Python (сокращение для -W default) или установив PYTHONWARNINGS=default в среде. Это включает обработку по умолчанию для всех предупреждений, включая те, которые по умолчанию игнорируются. Чтобы изменить действие, выполняемое для обнаруженных предупреждений, вы можете изменить аргумент, передаваемый в -W (например, -W error). Познакомьтесь с флагом -W для получения более подробной информации о том, что возможно.

Доступные функции

warnings.warn(message, category=None, stacklevel=1, source=None)

Выдать предупреждение, игнорировать его или вызвать исключение. Аргумент category, если он задан, должен быть классом категории предупреждения; по умолчанию — UserWarning. В качестве альтернативы message может быть экземпляром Warning, и в этом случае category будет проигнорирован и будет использоваться message.__class__. В этом случае текст сообщения будет str(message). Функция вызывает исключение, если фильтр предупреждений преобразовывает выданное предупреждение в ошибку. Аргумент stacklevel может использоваться функциями-обёртками, написанными на Python, например:

def deprecation(message):
    warnings.warn(message, DeprecationWarning, stacklevel=2)

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

source, если он указан — уничтоженный объект, который поднял ResourceWarning.

Изменено в версии 3.6: Добавлен параметр source.

warnings.warn_explicit(message, category, filename, lineno, module=None, registry=None, module_globals=None, source=None)

Низкоуровневый интерфейс функциональности warn(), явно передающий сообщение, категорию, имя файла и номер строки, а также, необязательно, имя модуля и реестр (который должен быть словарём модуля __warningregistry__). По умолчанию в качестве имени модуля используется имя файла с удаленным .py; если реестр не передан, предупреждение никогда не подавляется. message — строка, а category подкласс Warning или message может быть экземпляром Warning, и в этом случае category будет проигнорирован.

module_globals, если он указан, должен быть глобальным пространством имён, используемым кодом, для которого выдается предупреждение. (Аргумент используется для поддержки отображения источника для модулей, найденных в zip-файлах или других источниках импорта, не относящихся к файловой системе).

source, если он указан, — уничтоженный объект, который поднял ResourceWarning.

Изменено в версии 3.6: Добавлен параметр source.

warnings.showwarning(message, category, filename, lineno, file=None, line=None)

Записать предупреждение в файл. Реализация по умолчанию вызывает formatwarning(message, category, filename, lineno, line) и записывает полученную строку в file, который по умолчанию равен sys.stderr. Вы можете заменить эту функцию на любую вызываемую, присвоив warnings.showwarning. line — строка исходного кода, которая должна присутствовать в предупреждающем сообщении; если line не указан, showwarning() попытается прочитать строку, указанную filename и lineno.

warnings.formatwarning(message, category, filename, lineno, line=None)

Отформатировать предупреждение стандартным способом. Возвращает строку, которая может содержать встроенные символы новой строки и оканчивается новой строкой. line — строка исходного кода, которая должна добавляться в предупреждающее сообщение; если line не указан, formatwarning() попытается прочитать строку, указанную filename и lineno.

warnings.filterwarnings(action, message='', category=Warning, module='', lineno=0, append=False)

Вставить запись в список спецификаций фильтра предупреждений. По умолчанию запись вставляется спереди; если append — истина, он вставляется в конец. Она проверяет типы аргументов, компилирует регулярные выражения message и module и вставляет их в виде кортежа в список фильтров предупреждений. Записи, расположенные ближе к началу списка, переопределяют записи, расположенные ниже в списке, если обе соответствуют предупреждению. У пропущенных аргументов значение по умолчанию, соответствует всему.

warnings.simplefilter(action, category=Warning, lineno=0, append=False)

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

warnings.resetwarnings()

Сбросить фильтр предупреждений. Отменяет действие всех предыдущих вызовов filterwarnings(), в том числе параметров командной строки -W и вызовов simplefilter().

Доступные менеджеры контекста

class warnings.catch_warnings(*, record=False, module=None)

Менеджер контекста, копирующий и при выходе восстанавливающий фильтр предупреждений и функцию showwarning(). Если аргумент recordFalse (по умолчанию), менеджер контекста возвращает None при входе. Если recordTrue, возвращается список, постепенно заполняемый объектами, как это видно с помощью настраиваемой функции showwarning() (которая также подавляет вывод в sys.stdout). У каждого объекта в списке есть атрибуты с теми же именами, что и аргументы showwarning().

Аргумент module принимает модуль, используемый вместо данного модуля, возвращаемого при импорте warnings, фильтр которого будет защищён. Аргумент существует прежде всего для тестирования самого модуля warnings.

Примечание

Менеджер catch_warnings работает путём замены и последующего восстановления функции модуля showwarning() и внутреннего списка спецификаций фильтра. Это означает, что менеджер контекста изменяет глобальное состояние и, следовательно, не является потокобезопасным.