Режим розробки Python

Нове в версії 3.7.

Режим розробки Python вводить додаткові перевірки під час виконання, які занадто дорогі, щоб їх можна було ввімкнути за замовчуванням. Якщо код правильний, він не повинен бути більш детальним, ніж стандартний; нові попередження видаються лише тоді, коли виявляється проблема.

Його можна ввімкнути за допомогою параметра командного рядка -X dev або встановивши для змінної середовища PYTHONDEVMODE значення 1.

Ефекти режиму розробки Python

Увімкнення режиму розробки Python подібне до наступної команди, але з додатковими ефектами, описаними нижче:

PYTHONMALLOC=debug PYTHONASYNCIODEBUG=1 python3 -W default -X faulthandler

Наслідки режиму розробки Python:

  • Додайте за замовчуванням фільтр попереджень. Відображаються такі попередження:

    Зазвичай наведені вище попередження фільтруються стандартними фільтрами попереджень.

    Він поводиться так, ніби використовується параметр командного рядка -W за замовчуванням.

    Використовуйте параметр командного рядка -W error або встановіть для змінної середовища PYTHONWARNINGS значення error, щоб розглядати попередження як помилки.

  • Встановіть перехоплювачі налагодження на розподілювачі пам’яті, щоб перевірити:

    • Недоповнення буфера

    • Переповнення буфера

    • Порушення API розподілювача пам’яті

    • Небезпечне використання GIL

    Перегляньте функцію C PyMem_SetupDebugHooks().

    Він поводиться так, ніби для змінної середовища PYTHONMALLOC встановлено значення debug.

    Щоб увімкнути режим розробки Python, не встановлюючи налагоджувальні засоби розподілення пам’яті, встановіть для змінної середовища PYTHONMALLOC значення default.

  • Call faulthandler.enable() at Python startup to install handlers for the SIGSEGV, SIGFPE, SIGABRT, SIGBUS and SIGILL signals to dump the Python traceback on a crash.

    Він поводиться так, ніби використовується параметр командного рядка -X faulthandler або якщо змінна середовища PYTHONFAULTHANDLER має значення 1.

  • Увімкнути асинхронний режим налагодження. Наприклад, asyncio перевіряє співпрограми, які не були очікувані, і реєструє їх.

    Він поводиться так, ніби для змінної середовища PYTHONASYNCIODEBUG встановлено значення 1.

  • Перевірте аргументи encoding і errors для операцій кодування та декодування рядків. Приклади: open(), str.encode() і bytes.decode().

    За замовчуванням для найкращої продуктивності аргумент errors перевіряється лише при першій помилці кодування/декодування, а аргумент encoding іноді ігнорується для порожніх рядків.

  • Деструктор io.IOBase реєструє винятки close().

  • Set the dev_mode attribute of sys.flags to True.

Режим розробки Python не вмикає модуль tracemalloc за замовчуванням, оскільки накладні витрати (для продуктивності та пам’яті) будуть занадто великими. Увімкнення модуля tracemalloc надає додаткову інформацію про походження деяких помилок. Наприклад, ResourceWarning реєструє відстеження, де було виділено ресурс, а помилка переповнення буфера реєструє відстеження, де було виділено блок пам’яті.

Режим розробки Python не заважає параметру командного рядка -O видаляти оператори assert або встановлювати __debug__ значення False.

Змінено в версії 3.8: Деструктор io.IOBase тепер реєструє винятки close().

Змінено в версії 3.9: Аргументи encoding і errors тепер перевіряються на наявність операцій кодування та декодування рядків.

Приклад ResourceWarning

Приклад сценарію підрахунку кількості рядків текстового файлу, вказаного в командному рядку:

import sys

def main():
    fp = open(sys.argv[1])
    nlines = len(fp.readlines())
    print(nlines)
    # The file is closed implicitly

if __name__ == "__main__":
    main()

Сценарій не закриває файл явно. За замовчуванням Python не видає жодних попереджень. Приклад використання README.txt, який містить 269 рядків:

$ python3 script.py README.txt
269

Увімкнення режиму розробки Python відображає попередження ResourceWarning:

$ python3 -X dev script.py README.txt
269
script.py:10: ResourceWarning: unclosed file <_io.TextIOWrapper name='README.rst' mode='r' encoding='UTF-8'>
  main()
ResourceWarning: Enable tracemalloc to get the object allocation traceback

Крім того, увімкнення tracemalloc показує рядок, де було відкрито файл:

$ python3 -X dev -X tracemalloc=5 script.py README.rst
269
script.py:10: ResourceWarning: unclosed file <_io.TextIOWrapper name='README.rst' mode='r' encoding='UTF-8'>
  main()
Object allocated at (most recent call last):
  File "script.py", lineno 10
    main()
  File "script.py", lineno 4
    fp = open(sys.argv[1])

Виправлення полягає в тому, щоб явно закрити файл. Приклад використання контекстного менеджера:

def main():
    # Close the file explicitly when exiting the with block
    with open(sys.argv[1]) as fp:
        nlines = len(fp.readlines())
    print(nlines)

Якщо ресурс не закрити явно, він може залишитися відкритим набагато довше, ніж очікувалося; це може спричинити серйозні проблеми після виходу з Python. Це погано в CPython, але ще гірше в PyPy. Закриття ресурсів явно робить додаток більш детермінованим і надійнішим.

Приклад помилки неправильного дескриптора файлу

Сценарій, що відображає перший рядок самого себе:

import os

def main():
    fp = open(__file__)
    firstline = fp.readline()
    print(firstline.rstrip())
    os.close(fp.fileno())
    # The file is closed implicitly

main()

За замовчуванням Python не видає жодного попередження:

$ python3 script.py
import os

Режим розробки Python показує ResourceWarning і реєструє помилку «Поганий дескриптор файлу» під час завершення об’єкта файлу:

$ python3 script.py
import os
script.py:10: ResourceWarning: unclosed file <_io.TextIOWrapper name='script.py' mode='r' encoding='UTF-8'>
  main()
ResourceWarning: Enable tracemalloc to get the object allocation traceback
Exception ignored in: <_io.TextIOWrapper name='script.py' mode='r' encoding='UTF-8'>
Traceback (most recent call last):
  File "script.py", line 10, in <module>
    main()
OSError: [Errno 9] Bad file descriptor

os.close(fp.fileno()) закриває дескриптор файлу. Коли фіналізатор файлового об’єкта знову намагається закрити файловий дескриптор, це не вдається з помилкою Поганий файловий дескриптор. Файловий дескриптор має бути закрито лише один раз. У гіршому випадку подвійне закриття може призвести до збою (див. bpo-18748 для прикладу).

Виправлення полягає у видаленні рядка os.close(fp.fileno()) або відкритті файлу closefd=False.