warnings — Контроль предупреждений

Источник: Lib/warnings.py


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

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

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

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

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

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

См.также

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

Предупреждающие категории

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

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

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

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

Класс

Описание

Warning

Это базовый класс всех классов категорий предупреждений. Он является подклассом класса Exception.

UserWarning

Категория по умолчанию для warn().

DeprecationWarning

Базовая категория для предупреждений об устаревших функциях, когда эти предупреждения предназначены для других разработчиков Python (по умолчанию игнорируется, если не вызвано кодом в __main__).

SyntaxWarning

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

RuntimeWarning

Базовая категория для предупреждений о сомнительных функциях времени выполнения.

FutureWarning

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

PendingDeprecationWarning

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

ImportWarning

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

UnicodeWarning

Базовая категория для предупреждений, связанных с Unicode.

BytesWarning

Базовая категория для предупреждений, связанных с bytes и bytearray.

ResourceWarning

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

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

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

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

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

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

    Значение

    Утилизация

    "default"

    вывести первое появление соответствующих предупреждений для каждого места (модуль + номер строки), где было выдано предупреждение

    "error"

    Превратите предупреждения о совпадении в исключения

    "ignore"

    никогда не выводите предупреждения о совпадении

    "always"

    всегда выводить соответствующие предупреждения

    "module"

    вывести первое появление соответствующих предупреждений для каждого модуля, в котором выдано предупреждение (независимо от номера строки)

    "once"

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

  • message - это строка, содержащая регулярное выражение, которому должно соответствовать начало предупреждения (без учета регистра). В -W и PYTHONWARNINGS message - это литеральная строка, которую должно содержать начало предупреждения (без учета регистра), при этом игнорируются любые пробельные символы в начале или конце message.

  • категория - это класс (подкласс Warning), подклассом которого должна быть предупреждающая категория, чтобы соответствовать.

  • module - это строка, содержащая регулярное выражение, которому должно соответствовать начало полного имени модуля (с учетом регистра). В -W и PYTHONWARNINGS module - это литеральная строка, которой должно соответствовать полное имя модуля (с учетом регистра), игнорируя любые пробельные символы в начале или конце module.

  • lineno - целое число, которому должен соответствовать номер строки, где возникло предупреждение, или 0 для соответствия всем номерам строк.

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

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

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

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

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

action:message:category:module:line

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

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

default                      # Show all warnings (even those ignored by default)
ignore                       # Ignore all warnings
error                        # Convert all warnings to errors
error::ResourceWarning       # Treat ResourceWarning messages as errors
default::DeprecationWarning  # Show DeprecationWarning messages
ignore,default:::mymodule    # Only report warnings triggered by "mymodule"
error:::mymodule             # Convert warnings to errors in "mymodule"

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

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

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

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

При значении debug build список фильтров предупреждений по умолчанию пуст.

Изменено в версии 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") # Change the filter in this process
    os.environ["PYTHONWARNINGS"] = "default" # Also affect subprocesses

Наконец, разработчикам интерактивных оболочек, запускающих пользовательский код в пространстве имен, отличном от __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:
    # Cause all warnings to always be triggered.
    warnings.simplefilter("always")
    # Trigger a warning.
    fxn()
    # Verify some things
    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).

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

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

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

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

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

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

Аргумент skip_file_prefixes может быть использован для указания того, какие кадры стека игнорируются при подсчете уровней стека. Это может быть полезно, если вы хотите, чтобы предупреждение всегда появлялось в местах вызова вне пакета, когда постоянное значение stacklevel не подходит для всех путей вызова или его сложно поддерживать иным способом. Если указано, то это должен быть кортеж строк. Если указаны префиксы, stacklevel неявно переопределяется как max(2, stacklevel). Чтобы предупреждение было приписано вызывающей стороне, находящейся за пределами текущего пакета, можно написать:

# example/lower.py
_warn_skips = (os.path.dirname(__file__),)

def one_way(r_luxury_yacht=None, t_wobbler_mangrove=None):
    if r_luxury_yacht:
        warnings.warn("Please migrate to t_wobbler_mangrove=.",
                      skip_file_prefixes=_warn_skips)

# example/higher.py
from . import lower

def another_way(**kw):
    lower.one_way(**kw)

Таким образом, предупреждение обращается к сайтам вызова example.lower.one_way() и package.higher.another_way() только при вызове кода, находящегося вне пакета example.

Источник, если указан, - это уничтоженный объект, который выдал ResourceWarning.

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

Изменено в версии 3.12: Добавлена функция skip_file_prefixes.

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-файлах или других источниках импорта, не относящихся к файловой системе).

Источник, если указан, - это уничтоженный объект, который выдал 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)

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

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

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

warnings.resetwarnings()

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

@warnings.deprecated(msg, *, category=DeprecationWarning, stacklevel=1)

Декоратор, указывающий на то, что класс, функция или перегрузка являются устаревшими.

Когда этот декоратор применяется к объекту, во время выполнения при его использовании могут выдаваться предупреждения об устаревании. static type checkers также будет генерироваться диагностика использования устаревшего объекта.

Использование:

from warnings import deprecated
from typing import overload

@deprecated("Use B instead")
class A:
    pass

@deprecated("Use g instead")
def f():
    pass

@overload
@deprecated("int support is deprecated")
def g(x: int) -> int: ...
@overload
def g(x: str) -> int: ...

Предупреждение, указанное в category, будет выдаваться во время выполнения при использовании устаревших объектов. Для функций это происходит при вызове, для классов - при инстанцировании и создании подклассов. Если категория равна None, предупреждение не выдается во время выполнения. Параметр stacklevel определяет, где будет выдаваться предупреждение. Если он равен 1 (по умолчанию), предупреждение выдается непосредственному вызывающему лицу устаревшего объекта; если он выше, оно выдается дальше по стеку. Аргументы category и stacklevel не влияют на поведение статической проверки типов.

Сообщение об устаревании, переданное декоратору, сохраняется в атрибуте __deprecated__ декорируемого объекта. Если декоратор применяется к перегрузке, он должен быть после декоратора @overload, чтобы атрибут существовал в перегрузке, как возвращает typing.get_overloads().

Added in version 3.13: См. PEP 702.

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

class warnings.catch_warnings(*, record=False, module=None, action=None, category=Warning, lineno=0, append=False)

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

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

Если аргумент action не является None, остальные аргументы передаются в simplefilter(), как если бы он вызывался сразу после входа в контекст.

Примечание

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

Изменено в версии 3.11: Добавлены параметры action, category, lineno и append.