В Python код является данными. Функции, классы, модули и даже стек вызовов можно исследовать во время выполнения программы. Этот механизм называется интроспекцией.
Интроспекция активно используется во фреймворках, логировании, тестах, dependency injection контейнерах и отладчиках. При этом многие разработчики пользуются ей неосознанно.
Разберем, что это такое, зачем нужно и как применяется на практике.
Что такое интроспекция
Интроспекция это способность программы получать информацию о своей структуре и объектах во время выполнения.
Python позволяет узнать:
тип объекта
его атрибуты и методы
сигнатуру функции
параметры вызова
источник вызова функции
документацию и аннотации типов
При этом не требуется заранее знать конкретный объект.
Базовые инструменты интроспекции:
1. type() узнать тип объекта:
x = 10
print(type(x)) # <class 'int'>Используется при валидации данных, логировании, сериализации.
2. id() идентификатор объекта в памяти:
a = []
b = a
print(id(a) == id(b)) # TrueПомогает определить, ссылаются ли переменные на один и тот же объект.
3. dir() что содержит объект:
print(dir("hello")) # ['__add__', '__class__', '__contains__', ...]Возвращает список атрибутов и методов, доступных объекту.
Часто используется в REPL и во время отладки.
Проверка структуры объекта:
hasattr, getattr, setattr:
hasattr(obj, name)
Проверяет, существует ли у объекта атрибут с указанным именем. ВозвращаетTrueилиFalse.getattr(obj, name, default)
Возвращает значение атрибута объекта по имени. Если атрибут не найден, может вернуть значение по умолчанию или выброситьAttributeError.setattr(obj, name, value)
Динамически добавляет новый атрибут объекту или изменяет значение существующего атрибута.
class User:
def __init__(self, name):
self.name = name
u = User("Andrey")
print(hasattr(u, "name")) # True
print(getattr(u, "name")) # AndreyРеальный кейс это работа с динамическими объектами, JSON-данными, ORM.
Интроспекция функций:
Имя функции и документация:
"__name__" позволяет узнать имя функции
"__doc__" позволяет узнать документацию функции
def calculate(a, b):
"""Складывает два числа"""
return a + b
print(calculate.__name__) # calculate
print(calculate.__doc__) # Складывает два числаПрименяется при генерации документации и логировании.
Аннотации типов:
"__annotations__" это служебный атрибут Python, в котором хранятся аннотации типов для функций, методов, классов и переменных.
def process(data: list[str]) -> int:
return len(data)
print(process.__annotations__) # {'data': list[str], 'return': <class 'int'>}Фреймворки вроде FastAPI и Pydantic строят свою работу именно на этом механизме.
Модуль inspect: продвинутая интроспекция:
Сигнатура функции:
inspect.signature()анализирует вызываемый объект и возвращает его сигнатуру, то есть формальное описание того, какие аргументы принимает функция и что у них за параметры.
import inspect
def login(user: str, password: str, remember=False):
pass
sig = inspect.signature(login)
print(sig) # (user: str, password: str, remember=False)Используется для:
валидации аргументов
dependency injection
автогенерации CLI и API
Получение исходного кода функции
inspect.getsource()возвращает исходный код объекта в виде строки.
import inspect
print(inspect.getsource(login))
'''
def login(user: str, password: str, remember=False):
pass
'''Работает только если исходный код доступен и не был скомпилирован.
Интроспекция классов:
Атрибуты и методы класса:
Service.__dict__содержит все атрибуты и методы, определенные в классе, в виде словаря. Ключами являются имена атрибутов, а значениями сами объекты.
class Service:
version = "1.0"
def run(self):
pass
print(Service.__dict__.keys())
# dict_keys(['__module__', '__firstlineno__', 'version', 'run', '__static_attributes__', '__dict__', '__weakref__', '__doc__'])Позволяет динамически регистрировать методы, реализовывать плагины и анализировать API.
Интроспекция стека вызовов:
Определение вызывающей функции
import inspect
def who_called_me():
frame = inspect.currentframe()
caller = frame.f_back
print(caller.f_code.co_name)
def test():
who_called_me()
test() # testЧто здесь вообще происходит?
inspect.currentframe()возвращает объект текущего фрейма выполнения, то есть информацию о том месте, где сейчас выполняется код.frame.f_backсодержит ссылку на предыдущий фрейм в стеке вызовов, то есть на функцию, которая вызвала текущую.caller.f_code.co_nameэто имя функции, связанной с этим фреймом.
В результате функция who_called_me определяет, кто именно ее вызвал, и выводит имя этой функции.
Где интроспекция применяется в реальности:
FastAPI: анализ сигнатур и аннотаций
pytest: автоматический поиск тестов
ORM (Django, SQLAlchemy): анализ моделей
dependency injection контейнеры
CLI-генераторы
отладчики и логгеры
Когда интроспекцию использовать не стоит:
Не рекомендуется применять интроспекцию:
в горячих циклах из-за накладных расходов
для избыточной магии без документации
если задачу можно решить явным кодом
Интроспекция мощный инструмент, но чрезмерное использование ухудшает читаемость и поддержку кода.
Как результат:
Интроспекция в Python:
делает код гибким
позволяет создавать фреймворки
дает доступ к внутреннему устройству runtime
Использовать ее стоит осознанно и точечно.
Дополнительные бесплатные материалы и практические уроки по Python доступны в моем образовательном пространстве на Stepik.
