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

Интроспекция в Python на реальных примерах: как код узнает сам о себе, image #1

В 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

Использовать ее стоит осознанно и точечно.

Источник: https://habr.com/ru/articles/991498/

172 views·1 share