Практическое руководство по анализу программ на 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']