Практическое руководство по анализу программ на Python

Раздел: Прочее -> Анализ кода

Методы чтения и анализа кода Python

Основное средство: модуль inspect

Как получить исходный код, сигнатуру и документацию функции или класса?

Модуль inspect предоставляет функции для извлечения информации о живых объектах.


import inspect

def example_function(a, b=10):
    """Пример функции."""
    return a + b

print(inspect.getsource(example_function))
print(inspect.signature(example_function))
print(inspect.getdoc(example_function))

работа кода python (работа кода python)

def example_function(a, b=10):
    """Пример функции."""
    return a + b

(a, b=10) -> Any
Пример функции.

чтение кода python (чтение кода python)

Шаги: импортируем inspect, передаем объект функции в выбранную функцию. getsource возвращает исходный код, signature - объект сигнатуры, getdoc - строку документации.

Проблема: Для встроенных или скомпилированных на C функций getsource выбрасывает OSError. Решение: Обернуть вызов в try/except или предварительно проверить с помощью inspect.isfunction.

Вариант 1: Просмотр атрибутов через dir и type

Как перечислить все атрибуты и методы объекта?


class MyClass:
    def method_a(self): pass
    def method_b(self): pass

obj = MyClass()
print([attr for attr in dir(obj) if not attr.startswith('_')])

сравнение кода python (сравнение кода python)

['method_a', 'method_b']

dir() возвращает список имен, type() - класс объекта. Фильтрация убирает служебные атрибуты.

Проблема: Для объектов с динамическими атрибутами dir может не показывать все. Решение: Использовать __dict__ или vars().

Вариант 2: Чтение исходного файла модуля

Как получить код модуля без использования inspect?


import math
if hasattr(math, '__file__') and math.__file__:
    with open(math.__file__) as f:
        print(f.read()[:500])

Открываем файл, на который указывает __file__. Ограничиваем вывод первыми 500 символами.

Проблема: Некоторые встроенные модули (например, sys) не имеют __file__ или он ведет к бинарному файлу. Решение: Проверять через hasattr и тип файла.

Вариант 3: Трассировка выполнения с pdb

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


import pdb

def multiply(x, y):
    result = x * y
    return result

pdb.run('multiply(3, 4)')

После запуска откроется интерактивная оболочка Pdb. Команды: n (next), s (step into), p (print), q (quit).

Проблема: Внутри больших проектов сложно ориентироваться. Решение: Устанавливать точки останова с помощью pdb.set_trace() прямо в коде.

Вариант 4: Анализ байт-кода с dis

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


import dis

def greet(name):
    return f"Hello, {name}"

dis.dis(greet)
  2           0 LOAD_CONST               1 ('Hello, ')
              2 LOAD_FAST                0 (name)
              4 FORMAT_VALUE             0
              6 BUILD_STRING             2
              8 RETURN_VALUE

Каждая строка показывает адрес, инструкцию и аргументы. dis помогает понять, как Python исполняет код.

Проблема: Байт-код может различаться в разных версиях Python. Решение: Учитывать версию интерпретатора и не полагаться на конкретные опкоды без проверки.

Вариант 5: Справка с помощью help и __doc__

Как быстро получить документацию по объекту?


help(list.append)
print(list.append.__doc__)

help() открывает интерактивную справку, __doc__ возвращает строку документации.

Проблема: Если функция не имеет докстринга, возвращается None. Решение: Использовать inspect.getdoc, который может унаследовать документацию от родительского класса.

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

Пример

import inspect
import requests

req = requests.Request(method='GET', url='http://example.com')
print(inspect.getsource(requests.Request.__init__))
def __init__(self, method=None, url=None, headers=None, files=None, data=None, params=None, auth=None, cookies=None, hooks=None, json=None):
    self.method = method
    self.url = url
    # ... (продолжение)
Пример

import dis

def list_comp(n):
    return [i for i in range(n)]

def gen_expr(n):
    return (i for i in range(n))

print("List comprehension:")
dis.dis(list_comp)
print("\nGenerator expression:")
dis.dis(gen_expr)
List comprehension:
  2           0 LOAD_CONST               1 (<code object <listcomp> at ...)
              2 LOAD_CONST               2 ('list_comp.<locals>.<listcomp>')
              4 MAKE_FUNCTION            0
              6 LOAD_FAST                0 (n)
              8 GET_ITER
             10 CALL_FUNCTION            1
             12 RETURN_VALUE

Generator expression:
  2           0 LOAD_CONST               1 (<code object <genexpr> at ...)
              2 LOAD_CONST               2 ('gen_expr.<locals>.<genexpr>')
              4 MAKE_FUNCTION            0
              6 LOAD_FAST                0 (n)
              8 GET_ITER
             10 CALL_FUNCTION            1
             12 RETURN_VALUE
Пример

# debug_example.py
import pdb

def factorial(n):
    if n == 0:
        return 1
    else:
        pdb.set_trace()
        return n * factorial(n-1)

factorial(3)

Запуск: python -m pdb debug_example.py. После точки останова можно вводить команды.

> .../debug_example.py(8)factorial()
-> return n * factorial(n-1)
(Pdb) p n
3
(Pdb) n
> .../debug_example.py(8)factorial()
-> return n * factorial(n-1)
(Pdb) p n
2
Пример

def get_public_attrs(obj, max_depth=2, depth=0):
    if depth > max_depth:
        return
    attrs = [a for a in dir(obj) if not a.startswith('_')]
    print('  ' * depth + f"{type(obj).__name__}: {attrs}")
    for attr in attrs:
        val = getattr(obj, attr, None)
        if hasattr(val, '__dir__'):
            get_public_attrs(val, max_depth, depth+1)

get_public_attrs(42, max_depth=1)
int: ['bit_length', 'conjugate', 'denominator', 'from_bytes', 'imag', 'numerator', 'real', 'to_bytes']

Чтение кода Python - comments

En
чтение кода python (python)