Предупреждения Python: полное руководство по управлению
Управление предупреждениями в Python
Модуль warnings предоставляет гибкие инструменты для контроля вывода предупреждений. Это позволяет разработчику решать, какие предупреждения показывать, а какие скрывать, превращать их в исключения или перенаправлять в журнал. Ниже рассмотрены основные подходы с примерами.
Как настроить систему предупреждений для всего приложения?
Наиболее универсальный способ - вызвать warnings.filterwarnings() или warnings.simplefilter() в начале программы. Эти функции задают глобальный фильтр для всех последующих предупреждений.
import warnings
# Игнорировать предупреждения о депрекации
warnings.filterwarnings('ignore', category=DeprecationWarning)
# Пример вызова устаревшей функции
import numpy as np
np.array([1, 2, 3]) # если бы внутри был DeprecationWarning, он бы не отобразился
Warning python (предупреждения в python)
Параметры action могут принимать значения 'ignore', 'error', 'always', 'default', 'module' и 'once'. Категория задаёт тип предупреждения, на который распространяется правило.
- Вызов фильтра после того, как предупреждение уже было выдано (фильтр не применяется к уже сгенерированным предупреждениям).
- Забыть указать категорию - тогда правило применится ко всем предупреждениям, что может скрыть важные сообщения.
- Фильтры не работают в дочерних потоках, если они не настроены внутри каждого потока отдельно.
Как полностью отключить вывод любых предупреждений?
Используйте warnings.simplefilter('ignore') без указания категории. Это затронет все типы предупреждений.
import warnings
warnings.simplefilter('ignore')
# Теперь даже UserWarning не отобразится
warnings.warn('Тестовое предупреждение')
print('Предупреждение заблокировано')
Предупреждение заблокировано
Как превратить предупреждения в исключения?
Установите action='error' - предупреждение будет выброшено как исключение соответствующего класса (например, DeprecationWarning станет DeprecationWarning исключением).
import warnings
warnings.simplefilter('error', DeprecationWarning)
try:
# вызов устаревшей функции
import collections
collections.Sequence # в Python 3.9+ DeprecationWarning
except DeprecationWarning as e:
print(f'Перехвачено как исключение: {e}')
Перехвачено как исключение: ... (текст предупреждения)
Как игнорировать только определённый тип предупреждений?
Укажите категорию в warnings.filterwarnings. Например, оставить только FutureWarning:
import warnings
warnings.filterwarnings('ignore', category=DeprecationWarning)
warnings.filterwarnings('default', category=FutureWarning)
warnings.warn('Будущее предупреждение', FutureWarning)
warnings.warn('Устаревшее', DeprecationWarning)
.../script.py:5: FutureWarning: Будущее предупреждение
warnings.warn('Будущее предупреждение', FutureWarning)
Второе предупреждение не выводится, так как оно игнорируется.
filterwarnings.
Как временно подавить предупреждения внутри блока кода?
Используйте контекстный менеджер warnings.catch_warnings(). Внутри блока можно задать любые временные фильтры, которые исчезнут после выхода.
import warnings
with warnings.catch_warnings():
warnings.simplefilter('ignore')
# Код, в котором не нужны предупреждения
warnings.warn('Это предупреждение не появится')
# За пределами блока фильтр не действует
warnings.warn('А это появится', UserWarning)
.../script.py:8: UserWarning: А это появится
warnings.warn('А это появится', UserWarning)
catch_warningsне является потокобезопасным - в многопоточном коде могут возникнуть гонки, если одновременно несколько потоков изменяют фильтры.- При входе в контекст сохраняется снимок текущих фильтров, но если внутри блока изменить фильтры из другого потока, они могут испортиться.
Как управлять предупреждениями через командную строку?
Интерпретатор Python поддерживает флаги -W. Например, python -W ignore::DeprecationWarning script.py.
# В командной строке (не Python код):
python -W ignore::DeprecationWarning my_script.py
# Или более сложный фильтр:
python -W 'error::UserWarning' my_script.py
Флаг -W можно указывать несколько раз. Он эквивалентен вызову warnings.filterwarnings в начале скрипта.
Как задать глобальные правила предупреждений без изменения исходного кода?
Используйте переменную окружения PYTHONWARNINGS. Она принимает те же значения, что и -W.
# В bash:
export PYTHONWARNINGS="ignore::DeprecationWarning,error::UserWarning"
python script.py
# Или в Windows (cmd):
set PYTHONWARNINGS=ignore::DeprecationWarning,error::UserWarning
python script.py
После этого все скрипты, запущенные в данном сеансе, будут использовать указанные фильтры.
PYTHONWARNINGS=). Вложенные или конфликтующие правила могут привести к непредсказуемому поведению.
Как перехватить предупреждение и обработать его в своей логике?
Замените функцию warnings.showwarning на собственную. Это позволит, например, записывать предупреждения в лог-файл.
import warnings
import logging
logging.basicConfig(level=logging.DEBUG)
def my_showwarning(message, category, filename, lineno, file=None, line=None):
logging.warning(f'{category.__name__}: {message} ({filename}:{lineno})')
warnings.showwarning = my_showwarning
warnings.warn('Это будет в логе', UserWarning)
WARNING:root:UserWarning: Это будет в логе (script.py:10)
showwarning глобально влияет на все модули. Не забудьте восстановить оригинальную функцию после окончания работы, если это необходимо. Для временной замены используйте warnings.catch_warnings(record=True) (см. раздел примеров).
Расширенные примеры работы с предупреждениями
Создание пользовательской категории предупреждений
Можно определить свой класс, унаследованный от Warning, и использовать его для специфических предупреждений.
import warnings
class MySpecialWarning(Warning):
pass
def risky_function():
warnings.warn('Это пользовательское предупреждение', MySpecialWarning)
# Фильтруем только наше предупреждение
warnings.filterwarnings('error', category=MySpecialWarning)
try:
risky_function()
except MySpecialWarning as e:
print(f'Поймано пользовательское предупреждение: {e}')
Поймано пользовательское предупреждение: Это пользовательское предупреждение
Использование контекстного менеджера для записи предупреждений
warnings.catch_warnings(record=True) перехватывает все предупреждения внутри блока и сохраняет их в список.
import warnings
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter('always') # ловить все
warnings.warn('Первое', UserWarning)
warnings.warn('Второе', DeprecationWarning)
print(f'Перехвачено {len(w)} предупреждений')
for warning in w:
print(f'Категория: {warning.category.__name__}, сообщение: {warning.message}')
Перехвачено 2 предупреждений Категория: UserWarning, сообщение: Первое Категория: DeprecationWarning, сообщение: Второе
Фильтрация по имени модуля
Можно настроить игнорирование предупреждений только из определённого модуля, используя параметр module.
import warnings
# Игнорировать DeprecationWarning только из модуля 'numpy'
warnings.filterwarnings('ignore', category=DeprecationWarning, module='numpy')
import numpy as np
# Если numpy когда-нибудь выдаст DeprecationWarning, он будет скрыт
Параметр module принимает регулярное выражение (через re). Например, 'numpy\.*'.
Сброс всех фильтров к состоянию по умолчанию
warnings.resetwarnings() удаляет все заданные программой фильтры и возвращает стандартное поведение.
import warnings
warnings.simplefilter('ignore')
print('Фильтры изменены')
warnings.resetwarnings()
print('Фильтры сброшены')
warnings.warn('Это снова видно')
Фильтры изменены Фильтры сброшены script.py:7: UserWarning: Это снова видно
Предупреждения в многопоточном коде
Каждый поток имеет собственный стек фильтров, но начальные настройки копируются из главного потока. Изменения в одном потоке не влияют на другие, если не использовать специальные механизмы синхронизации.
import warnings
import threading
def worker():
# В дочернем потоке другой контекст
with warnings.catch_warnings():
warnings.simplefilter('ignore')
warnings.warn('Из потока не видно')
# Здесь предупреждение не подавлено, если не настроено глобально
warnings.warn('Из потока видно')
thread = threading.Thread(target=worker)
thread.start()
thread.join()
# В главном потоке свой контекст
warnings.warn('Главный поток')
Результат зависит от того, какие фильтры установлены в главном потоке. В примере выше в главном потоке никакие фильтры не менялись, поэтому все три предупреждения (кроме первого, которое подавлено внутри catch_warnings) будут выведены.
Логирование предупреждений через модуль logging
Можно перенаправить все предупреждения в систему логирования, используя кастомный обработчик.
import warnings
import logging
logging.basicConfig(level=logging.WARNING, format='%(levelname)s - %(message)s')
def log_warning(message, category, filename, lineno, file=None, line=None):
logger = logging.getLogger(category.__name__)
logger.warning(f'{filename}:{lineno}: {message}')
warnings.showwarning = log_warning
warnings.warn('Тест лога')
WARNING - UserWarning: script.py:10: Тест лога
Тестирование кода с предупреждениями (pytest)
Библиотека pytest предоставляет встроенный контекст pytest.warns для проверки, что код выдает определённое предупреждение.
# content of test_warnings.py
import warnings
import pytest
def deprecated_function():
warnings.warn('use new version', DeprecationWarning)
def test_deprecation():
with pytest.warns(DeprecationWarning):
deprecated_function()
$ pytest test_warnings.py -v ... test_warnings.py::test_deprecation PASSED
Если предупреждение не будет выдано, тест упадет.