> **Источник:** https://python-all.ru/3.15/library/profile.html
>
> «Документация Python на русском» – неофициальный перевод официальной документации Python: версии от 2.6 до 3.16, полнотекстовый поиск, английский оригинал рядом с переводом. Эта Markdown-версия страницы предназначена для работы с LLM: вставьте её в ChatGPT, Claude или Cursor.

---

# `profile` – Профилировщик на чистом Python

**Исходный код:** [Lib/profile.py](https://python-all.ru/src/3.15/Lib/profile.py)

---

Устарело с версии 3.15, будет удалено в версии 3.17.

Модуль `profile` устарел и будет удалён в Python 3.17. Используйте вместо него [`profiling.tracing`](https://python-all.ru/3.15/library/profiling.tracing.html#module-profiling.tracing).

Модуль `profile` содержит реализацию детерминированного профилировщика на чистом Python. Хотя он может пригодиться для изучения внутреннего устройства профилировщика или расширения его поведения через наследование, его реализация на чистом Python вносит значительные накладные расходы по сравнению с C-модулем [`profiling.tracing`](https://python-all.ru/3.15/library/profiling.tracing.html#module-profiling.tracing).

Для большинства задач профилирования используйте:

- [`profiling.sampling`](https://python-all.ru/3.15/library/profiling.sampling.html#module-profiling.sampling) для отладки в продакшене с нулевыми накладными расходами
- [`profiling.tracing`](https://python-all.ru/3.15/library/profiling.tracing.html#module-profiling.tracing) для разработки и тестирования

## Миграция

Переход с `profile` на [`profiling.tracing`](https://python-all.ru/3.15/library/profiling.tracing.html#module-profiling.tracing) не представляет сложности. API совместимы:

```python
# Старое (устарело)
import profile
profile.run('my_function()')

# Новое (рекомендуется)
import profiling.tracing
profiling.tracing.run('my_function()')
```

В большинстве случаев достаточно заменить `import profile` на `import profiling.tracing` (и использовать `profiling.tracing` вместо `profile` по всему коду) – это прямой путь миграции.

> **Примечание**
>
> Модуль `cProfile` остаётся доступным как обратно совместимый псевдоним [`profiling.tracing`](https://python-all.ru/3.15/library/profiling.tracing.html#module-profiling.tracing). Существующий код, использующий `import cProfile`, продолжит работу без изменений.

## `profile` и `profiling.tracing` – справочник по модулям

Оба модуля, `profile` и [`profiling.tracing`](https://python-all.ru/3.15/library/profiling.tracing.html#module-profiling.tracing), предоставляют следующие функции:

#### `profile.run(command, filename=None, sort=-1)`

Эта функция принимает один аргумент, который можно передать функции [`exec()`](https://python-all.ru/3.15/library/functions.html#exec), и необязательное имя файла. Во всех случаях эта процедура выполняет:

```python
exec(command, __main__.__dict__, __main__.__dict__)
```

и собирает статистику профилирования из выполнения. Если имя файла не указано, эта функция автоматически создаёт экземпляр [`Stats`](https://python-all.ru/3.15/library/pstats.html#pstats.Stats) и выводит простой отчёт профилирования. Если указано значение сортировки, оно передаётся этому экземпляру `Stats` для управления сортировкой результатов.

#### `profile.runctx(command, globals, locals, filename=None, sort=-1)`

Эта функция похожа на [`run()`](https://python-all.ru/3.15/library/profile.html#profile.run), но с дополнительными аргументами для указания словарей глобальных и локальных переменных для строки *команда*. Эта процедура выполняет:

```python
exec(command, globals, locals)
```

и собирает статистику профилирования, как в функции [`run()`](https://python-all.ru/3.15/library/profile.html#profile.run) выше.

#### `class profile.Profile(timer=None, timeunit=0.0, subcalls=True, builtins=True)`

Этот класс обычно используется только тогда, когда требуется более точный контроль профилирования, чем предоставляет функция [`profiling.tracing.run()`](https://python-all.ru/3.15/library/profiling.tracing.html#profiling.tracing.run).

Можно указать собственный таймер для измерения времени выполнения кода с помощью аргумента *timer*. Это должна быть функция, возвращающая одно число, представляющее текущее время. Если число целое, *timeunit* задаёт множитель, определяющий длительность каждой единицы времени. Например, если таймер возвращает время, измеряемое в тысячных долях секунды, единица времени будет `.001`.

Непосредственное использование класса `Profile` позволяет форматировать результаты профилирования без записи данных профиля в файл:

```python
import profiling.tracing
import pstats
import io
from pstats import SortKey

pr = profiling.tracing.Profile()
pr.enable()
# ... сделать что-то ...
pr.disable()
s = io.StringIO()
sortby = SortKey.CUMULATIVE
ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
ps.print_stats()
print(s.getvalue())
```

Класс `Profile` также можно использовать как контекстный менеджер (поддерживается только в [`profiling.tracing`](https://python-all.ru/3.15/library/profiling.tracing.html#module-profiling.tracing), но не в устаревшем модуле `profile`; см. [Типы контекстных менеджеров](https://python-all.ru/3.15/library/stdtypes.html#typecontextmanager)):

```python
import profiling.tracing

with profiling.tracing.Profile() as pr:
    # ... сделать что-то ...

    pr.print_stats()
```

Изменено в версии 3.8: Добавлена поддержка контекстного менеджера.

#### `enable()`

Начать сбор данных профилирования. Только в [`profiling.tracing`](https://python-all.ru/3.15/library/profiling.tracing.html#module-profiling.tracing).

#### `disable()`

Остановить сбор данных профилирования. Только в [`profiling.tracing`](https://python-all.ru/3.15/library/profiling.tracing.html#module-profiling.tracing).

#### `create_stats()`

Остановить сбор данных профилирования и сохранить результаты внутри как текущий профиль.

#### `print_stats(sort=-1)`

Создать объект [`Stats`](https://python-all.ru/3.15/library/pstats.html#pstats.Stats) на основе текущего профиля и вывести результаты в stdout.

Параметр *sort* задаёт порядок сортировки отображаемой статистики. Он принимает один ключ или кортеж ключей для многоуровневой сортировки, как в [`pstats.Stats.sort_stats()`](https://python-all.ru/3.15/library/pstats.html#pstats.Stats.sort_stats).

Добавлено в версии 3.13: `print_stats()` теперь принимает кортеж ключей.

#### `dump_stats(filename)`

Записать результаты текущего профиля в *filename*.

#### `run(cmd)`

Профилировать cmd с помощью [`exec()`](https://python-all.ru/3.15/library/functions.html#exec).

#### `runctx(cmd, globals, locals)`

Профилировать cmd с помощью [`exec()`](https://python-all.ru/3.15/library/functions.html#exec) с указанным глобальным и локальным окружением.

#### `runcall(func, /, *args, **kwargs)`

Профилировать `func(*args, **kwargs)`

Обратите внимание, что профилирование будет работать, только если вызванная команда/функция действительно возвращает управление. Если интерпретатор завершается (например, вызовом [`sys.exit()`](https://python-all.ru/3.15/library/sys.html#sys.exit) во время выполнения вызванной команды/функции), результаты профилирования не будут выведены.

## Отличия от `profiling.tracing`

Модуль `profile` отличается от [`profiling.tracing`](https://python-all.ru/3.15/library/profiling.tracing.html#module-profiling.tracing) в нескольких аспектах:

**Бо́льшие накладные расходы.** Реализация на чистом Python существенно медленнее C-реализации, поэтому она не подходит для профилирования долго работающих программ или кода, чувствительного к производительности.

**Поддержка калибровки.** Модуль `profile` поддерживает калибровку для компенсации накладных расходов профилирования. В [`profiling.tracing`](https://python-all.ru/3.15/library/profiling.tracing.html#module-profiling.tracing) она не нужна, потому что C-реализация даёт пренебрежимо малые накладные расходы.

**Пользовательские таймеры.** Оба модуля поддерживают пользовательские таймеры, но `profile` принимает функции-таймеры, возвращающие кортежи (например, [`os.times()`](https://python-all.ru/3.15/library/os.html#os.times)), а [`profiling.tracing`](https://python-all.ru/3.15/library/profiling.tracing.html#module-profiling.tracing) требует функцию, возвращающую одно число.

**Создание подклассов.** Чистая реализация на Python упрощает создание подклассов и расширение для настройки поведения профилирования.

## Что такое детерминированное профилирование?

*Детерминированное профилирование* означает, что отслеживаются все события *вызова функции*, *возврата из функции* и *исключения*, и выполняются точные замеры времени для интервалов между этими событиями (в течение которых выполняется пользовательский код). В отличие от этого, *статистическое профилирование* (которое предоставляется модулем [`profiling.sampling`](https://python-all.ru/3.15/library/profiling.sampling.html#module-profiling.sampling)) периодически сэмплирует актуальный указатель инструкций и выводит предположения о том, где тратится время. Последний метод традиционно имеет меньшие накладные расходы (поскольку код не требует инструментирования), но даёт лишь относительные указания на то, где расходуется время.

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

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

## Ограничения

Одно ограничение связано с точностью информации о времени. Существует фундаментальная проблема с детерминированными профилировщиками, касающаяся точности. Наиболее очевидное ограничение состоит в том, что внутренние «часы» тикают с частотой (обычно) около 0,001 секунды. Следовательно, ни одно измерение не может быть точнее этих часов. Если сделать достаточно измерений, «ошибка» будет стремиться к усреднению. К сожалению, устранение этой первой ошибки приводит ко второму источнику ошибок.

The second problem is that it “takes a while” from when an event is dispatched until the profiler’s call to get the time actually *gets* the state of the clock. Similarly, there is a certain lag when exiting the profiler event handler from the time that the clock’s value was obtained (and then squirreled away), until the user’s code is once again executing. As a result, functions that are called many times, or call many functions, will typically accumulate this error. The error that accumulates in this fashion is typically less than the accuracy of the clock (less than one clock tick), but it *can* accumulate and become very significant.

Проблема более значима для устаревшего модуля `profile`, чем для модуля с меньшими накладными расходами [`profiling.tracing`](https://python-all.ru/3.15/library/profiling.tracing.html#module-profiling.tracing). По этой причине `profile` предоставляет возможность калибровки для конкретной платформы, чтобы эту ошибку можно было вероятностно (в среднем) устранить. После калибровки профилировщик станет точнее (в смысле наименьших квадратов), но иногда будет выдавать отрицательные числа (когда количество вызовов исключительно мало, и боги вероятности не на вашей стороне :-)). *Не* пугайтесь отрицательных чисел в профиле. Они должны *только* появляться, если вы откалибровали профилировщик, и результаты на самом деле лучше, чем без калибровки.

## Калибровка

Профилировщик модуля `profile` вычитает константу из времени обработки каждого события, чтобы компенсировать накладные расходы на вызов функции времени и сохранение результатов. По умолчанию константа равна 0. Следующую процедуру можно использовать для получения лучшей константы для данной платформы (см. [Ограничения](https://python-all.ru/3.15/library/profile.html#profile-limitations)).

```python
import profile
pr = profile.Profile()
for i in range(5):
    print(pr.calibrate(10000))
```

Метод выполняет количество вызовов Python, заданное аргументом, напрямую и снова под профилировщиком, измеряя время для обоих случаев. Затем он вычисляет скрытые накладные расходы на одно событие профилировщика и возвращает это значение в виде числа с плавающей запятой. Например, на процессоре Intel Core i5 с частотой 1,8 ГГц под управлением macOS и с использованием time.process\_time() в Python в качестве таймера магическое число составляет около 4,04e-6.

Цель этого упражнения – получить достаточно стабильный результат. Если ваш компьютер *очень* быстрый или функция таймера имеет низкое разрешение, возможно, придётся передать 100000 или даже 1000000, чтобы получить стабильные результаты.

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

```python
import profile

# 1. Применить вычисленное смещение ко всем экземплярам Profile, создаваемым в дальнейшем.
profile.Profile.bias = your_computed_bias

# 2. Применить вычисленное смещение к конкретному экземпляру Profile.
pr = profile.Profile()
pr.bias = your_computed_bias

# 3. Указать вычисленное смещение в конструкторе экземпляра.
pr = profile.Profile(bias=your_computed_bias)
```

Если есть выбор, лучше выбрать меньшую константу – тогда результаты будут «реже» отображаться как отрицательные в статистике профилирования.

## Использование пользовательского таймера

Если требуется изменить способ определения текущего времени (например, принудительно использовать wall-clock time или время процесса), передайте нужную функцию таймера в конструктор класса [`Profile`](https://python-all.ru/3.15/library/profile.html#profile.Profile):

```python
pr = profile.Profile(your_time_func)
```

Полученный профилировщик будет вызывать `your_time_func`. В зависимости от того, используется ли [`profile.Profile`](https://python-all.ru/3.15/library/profile.html#profile.Profile) или [`profiling.tracing.Profile`](https://python-all.ru/3.15/library/profiling.tracing.html#profiling.tracing.Profile), возвращаемое значение `your_time_func` будет интерпретироваться по-разному:

**[`profile.Profile`](https://python-all.ru/3.15/library/profile.html#profile.Profile)**

`your_time_func` должна возвращать одно число или список чисел, сумма которых равна текущему времени (как [`os.times()`](https://python-all.ru/3.15/library/os.html#os.times)). Если функция возвращает одно число времени или длина возвращаемого списка равна 2, то будет использована особенно быстрая версия диспетчерской подпрограммы.

Следует помнить, что необходимо откалибровать класс профилировщика для выбранной функции таймера (см. [Калибровка](https://python-all.ru/3.15/library/profile.html#profile-calibration)). Для большинства машин таймер, возвращающий одно целое число, даёт наилучшие результаты с точки зрения низких накладных расходов при профилировании. ([`os.times()`](https://python-all.ru/3.15/library/os.html#os.times) *довольно* плох, так как возвращает кортеж чисел с плавающей запятой). Если требуется заменить таймер на более качественный наиболее чистым способом, создайте производный класс и жёстко задайте замещающий метод диспетчеризации, который наилучшим образом обрабатывает вызов таймера, вместе с соответствующим калибровочным коэффициентом.

**[`profiling.tracing.Profile`](https://python-all.ru/3.15/library/profiling.tracing.html#profiling.tracing.Profile)**

`your_time_func` должна возвращать одно число. Если она возвращает целые числа, можно также вызвать конструктор класса со вторым аргументом, указывающим реальную длительность одной единицы времени. Например, если `your_integer_time_func` возвращает время, измеряемое в тысячах секунд, экземпляр [`Profile`](https://python-all.ru/3.15/library/profile.html#profile.Profile) конструируется следующим образом:

```python
pr = profiling.tracing.Profile(your_integer_time_func, 0.001)
```

Поскольку класс [`profiling.tracing.Profile`](https://python-all.ru/3.15/library/profiling.tracing.html#profiling.tracing.Profile) не может быть откалиброван, пользовательские функции таймера следует использовать с осторожностью и делать их как можно более быстрыми. Для достижения наилучших результатов с пользовательским таймером может потребоваться жёстко прописать его в коде C внутреннего модуля `_lsprof`.

Python 3.3 добавляет несколько новых функций в [`time`](https://python-all.ru/3.15/library/time.html#module-time), которые можно использовать для точных измерений процессорного или реального времени. Например, см. [`time.perf_counter()`](https://python-all.ru/3.15/library/time.html#time.perf_counter).

> **См. также**
>
> **[`profiling`](https://python-all.ru/3.15/library/profiling.html#module-profiling)**
>
> Обзор инструментов профилирования Python.
>
> **[`profiling.tracing`](https://python-all.ru/3.15/library/profiling.tracing.html#module-profiling.tracing)**
>
> Рекомендуемая замена для этого модуля.
>
> **[`pstats`](https://python-all.ru/3.15/library/pstats.html#module-pstats)**
>
> Статистический анализ и форматирование данных профиля.
