Визуализация кода на Python

Раздел: Инструменты -> Визуализация кода

Визуализация кода Python: обзор инструментов и подходов

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

Главный инструмент: code2flow

Как построить граф потока управления для всего проекта?

Утилита code2flow анализирует исходный код Python и создаёт визуальный граф вызовов функций. Установка проста:

pip install code2flow

визуализация кода python (визуализация кода python)

Затем для файла или папки выполняется команда:

code2flow my_script.py -o output.png

Инструмент автоматически находит все функции и строит граф их взаимных вызовов. Формат вывода может быть PNG, SVG, DOT или JSON.

Возможные проблемы:

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

Вариант 1: ручное построение графа с Graphviz

Как нарисовать произвольные диаграммы зависимостей без готового анализатора?

Библиотека graphviz позволяет создавать графы «с нуля». Пример для визуализации структуры классов:

from graphviz import Digraph

dot = Digraph('project')
dot.node('A', 'ClassA')
dot.node('B', 'ClassB')
dot.edge('A', 'B', label='depends on')
dot.render('classes.gv', view=True)

Такой подход удобен для небольших проектов, но требует ручного описания всех связей.

Типичная ошибка:

Игнорирование вложенных структур (например, методов внутри классов). Для корректного отображения лучше сначала получить AST.

Вариант 2: визуализация AST (синтаксическое дерево)

Как визуально исследовать структуру кода до его выполнения?

Модуль ast вместе с graphviz позволяет нарисовать дерево разбора:

import ast
from graphviz import Digraph

code = """
def hello():
    print('Hi')
"""
tree = ast.parse(code)
dot = Digraph()
def visit(node, parent=None):
    name = type(node).__name__
    dot.node(str(id(node)), name)
    if parent:
        dot.edge(str(id(parent)), str(id(node)))
    for child in ast.iter_child_nodes(node):
        visit(child, node)
visit(tree)
dot.render('ast.gv', view=True)

Это даёт полное представление о синтаксической структуре. Подходит для обучения, отладки парсеров.

Сложность:

Для больших файлов дерево становится огромным. Лучше фильтровать узлы (например, только FunctionDef).

Вариант 3: граф вызовов с pycallgraph

Как визуализировать реальные вызовы функций во время исполнения?

Хотя библиотека pycallgraph сейчас не обновляется, она всё ещё работает для простых случаев. Установка:

pip install pycallgraph

Пример декорирования точки входа:

from pycallgraph import PyCallGraph
from pycallgraph.output import GraphvizOutput

with PyCallGraph(output=GraphvizOutput()):
    # код, который нужно отследить
    my_function()

По окончании создаётся PNG-файл с графом. Однако для современных проектов (асинхронность, декораторы) лучше перейти на code2flow.

Известные ошибки:

pycallgraph не отслеживает вызовы в сторонних библиотеках, если они не помечены особым образом. Возможны зацикливания.

Вариант 4: анализ зависимостей модулей с pyan3

Как увидеть, какие модули импортируют другие?

Инструмент pyan3 строит граф импортов. Установка:

pip install pyan3

Команда:

pyan3 my_project/*.py --dot -o imports.dot
# затем преобразовать в PNG:
dot -Tpng -o imports.png imports.dot

Это помогает реорганизовать проект, избавиться от циклических зависимостей.

Ограничение:

pyan3 анализирует только статические импорты (не динамические через importlib).

Вариант 5: визуализация профилирования с snakeviz

Как визуально изучить результаты профилирования?

Вместо текстовых отчётов cProfile можно открыть интерактивный график в browser через snakeviz:

python -m cProfile -o profile.prof my_script.py
snakeviz profile.prof

В браузере появится круговой граф, где размер секторов соответствует времени выполнения функций. Удобно для поиска узких мест.

Нюансы:

Для асинхронного кода профилирование требует отдельной настройки. snakeviz не показывает вызовы, если функция вызвана миллионы раз – граф становится трудночитаемым.

Расширенные примеры визуализации кода

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

Пример 1: code2flow для многофайлового проекта

Пусть есть два файла: utils.py и main.py. В utils.py определена функция helper, в main.py – функция process, которая вызывает helper.

Пример
# utils.py
def helper(data):
    return data * 2

# main.py
from utils import helper
def process():
    result = helper(10)
    print(result)
if __name__ == '__main__':
    process()

Запуск code2flow:

Пример
code2flow main.py -o project_flow.png

Результат (граф в PNG) показывает две функции: process (вызывает helper) и helper (вызывается из process). Добавление опции --hide-unreachable скрывает функции, не имеющие входных точек.

[изображение: граф с двумя узлами process->helper]

Пример 2: AST визуализация с фильтрацией узлов

Визуализируем только функции и классы из файла, игнорируя вызовы и выражения:

Пример
import ast, graphviz

code = open('example.py').read()
tree = ast.parse(code)
dot = Digraph(format='png')
for node in ast.walk(tree):
    if isinstance(node, (ast.FunctionDef, ast.ClassDef)):
        name = node.name
        dot.node(name, name)
        # поиск вызовов внутри тела
        for child in ast.walk(node):
            if isinstance(child, ast.Call):
                if isinstance(child.func, ast.Name):
                    called = child.func.id
                    dot.edge(name, called)
dot.render('ast_filtered.png', view=True)

Результат – граф вызовов, построенный на основе синтаксического анализа, без запуска кода.

[изображение: граф с узлами main и helper, стрелка от main к helper]

Пример 3: pyan3 с группировкой по пакетам

Структура проекта:

Пример
project/
  __init__.py
  core.py (импортирует utils.helpers)
  utils/
    __init__.py
    helpers.py
    parsing.py

Запуск pyan3 с группировкой в кластеры:

Пример
pyan3 project/*.py project/utils/*.py --dot --no-defines -o imports.dot
dot -Tpng imports.dot -o imports.png

Опция --no-defines убирает функции, оставляя только модули. Результат – граф, где видно, что core.py зависит от utils/helpers.py.

[изображение: модули с иерархией импортов]

Пример 4: snakeviz с фильтрацией по времени

Профилируем скрипт, который много раз вызывает io-операции:

Пример
import time, random
def io_task():
    time.sleep(random.uniform(0.01, 0.1))

def heavy():
    for _ in range(100):
        io_task()

if __name__ == '__main__':
    import cProfile
    cProfile.run('heavy()', sort='time')

Сохраняем профиль и открываем в snakeviz – видно, что io_task занимает 95% времени. Можно кликнуть на функцию, чтобы увидеть её вызовы.

[в браузере: круговая диаграмма с большим сектором io_task]

Пример 5: динамический граф вызовов с trace и graphviz

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

Пример
import trace
from graphviz import Digraph

# создаём объект trace
tracer = trace.Trace(count=True, trace=True, ignoredirs=[sys.prefix, sys.exec_prefix])
tracer.runfunc(my_function)
# получаем статистику вызовов
results = tracer.results()
# преобразуем в граф (упрощённо)
dot = Digraph()
for call, count in results.counts.items():
    caller, callee = call
    dot.node(caller, str(caller))
    dot.node(callee, str(callee))
    dot.edge(caller, callee, label=str(count))
dot.render('callgraph.png')

Этот способ даёт более детальный граф, чем code2flow, но требует больше кода.

[изображение: граф с числами вызовов на рёбрах]

Визуализация кода Python - comments

En
визуализация кода python (python)