inspect en Python: introspección de objetos

Publicado el: 02/08/2026
Tempo de leitura: 6 minutos
Análisis de software que representa introspección de objetos con inspect en Python

Frameworks, depuradores, sistemas de plugins, generadores de documentación y herramientas de prueba necesitan descubrir cómo fue definido un objeto Python. Comprueban si algo es función, clase, generator o coroutine, enumeran miembros, leen firmas, recuperan código fuente y examinan la pila de ejecución. El módulo inspect en Python reúne estas operaciones de introspección en la biblioteca estándar.

Esta guía explica getmembers(), los predicados is*, signature(), Signature.bind(), getsource(), unwrap(), getattr_static() y funciones de frames. Complementa nuestros artículos sobre descriptors en Python, singledispatch, copias de objetos, diagnóstico de rendimiento y contextvars.

Qué es la introspección

La introspección es la capacidad de un programa para examinar objetos durante la ejecución. Las funciones, clases, módulos y métodos de Python contienen metadatos como nombre, módulo, documentación, annotations y referencias al código compilado.

def calcular_total(valor: float, tasa: float = 0.1) -> float:
    """Calcula un total con una tasa."""
    return valor * (1 + tasa)

print(calcular_total.__name__)
print(calcular_total.__doc__)
print(calcular_total.__annotations__)

inspect ofrece una interfaz uniforme y herramientas aplicables a varias categorías.

Identificar categorías de objetos

Predicados como isfunction(), ismethod(), isclass() e ismodule() expresan claramente la intención.

import inspect

class Servicio:
    def ejecutar(self):
        return "ok"

servicio = Servicio()

print(inspect.isclass(Servicio))
print(inspect.isfunction(Servicio.ejecutar))
print(inspect.ismethod(servicio.ejecutar))
print(inspect.isroutine(servicio.ejecutar))

El acceso mediante la clase devuelve la función definida; mediante una instancia produce un método vinculado. Esta diferencia importa en registradores e inyección de dependencias.

Generators, coroutines y async generators

El módulo distingue las funciones que crean objetos asíncronos de los objetos ya creados.

import inspect

async def buscar():
    return 42

async def eventos():
    yield "inicio"

def numeros():
    yield 1

print(inspect.iscoroutinefunction(buscar))
print(inspect.isasyncgenfunction(eventos))
print(inspect.isgeneratorfunction(numeros))

Después de llamar, usa iscoroutine(), isasyncgen(), isgenerator() o isawaitable(). Espera o cierra los objetos creados en pruebas.

Enumerar miembros con getmembers()

getmembers() devuelve pares (nombre, valor) ordenados.

class Producto:
    categoria = "general"

    def __init__(self, nombre):
        self.nombre = nombre

    def resumen(self):
        return self.nombre

for nombre, valor in inspect.getmembers(Producto):
    if not nombre.startswith("__"):
        print(nombre, valor)

Un predicado opcional filtra la salida:

metodos = inspect.getmembers(Producto, inspect.isfunction)
print([nombre for nombre, _ in metodos])

Esta combinación resulta útil para descubrir handlers, comandos y pruebas.

getmembers() puede ejecutar código

La búsqueda normal activa descriptors, properties, __getattr__() y __getattribute__(). Inspeccionar un objeto puede ejecutar lógica.

class Ejemplo:
    @property
    def peligroso(self):
        print("property ejecutada")
        return 10

inspect.getmembers(Ejemplo())

Esto puede provocar efectos secundarios en herramientas de documentación y objetos no confiables.

Inspección pasiva con getmembers_static()

Desde Python 3.11, getmembers_static() evita la resolución dinámica.

miembros = inspect.getmembers_static(Ejemplo())
for nombre, valor in miembros:
    if nombre == "peligroso":
        print(valor)  # descriptor property, sin ejecutar getter

Puede devolver el descriptor en vez del valor y omitir atributos dinámicos. Elige según necesites comportamiento real o estructura estática.

getattr_static()

getattr_static() recupera un atributo sin activar descriptors, __getattr__ o __getattribute__.

descriptor = inspect.getattr_static(Ejemplo, "peligroso")
print(type(descriptor))

Es adecuado para analizadores y depuradores. Resolver manualmente un descriptor aún puede ejecutar código.

Documentación con getdoc()

getdoc() limpia indentación y puede heredar documentación de clases, métodos, propiedades y descriptors.

print(inspect.getdoc(calcular_total))

cleandoc() normaliza una cadena de documentación independiente.

Localizar archivo y módulo

getfile(), getsourcefile() y getmodule() ayudan a herramientas de desarrollo.

print(inspect.getfile(calcular_total))
print(inspect.getsourcefile(calcular_total))
print(inspect.getmodule(calcular_total))

Built-ins y extensiones en C pueden no tener archivo fuente Python. Captura TypeError y admite None.

Recuperar código fuente

getsource() devuelve texto fuente para módulos, clases, funciones, métodos, frames, tracebacks y objetos de código compatibles.

try:
    fuente = inspect.getsource(calcular_total)
    print(fuente)
except (OSError, TypeError):
    print("Código fuente no disponible")

La documentación oficial de inspect indica que OSError aparece cuando la fuente no puede recuperarse y TypeError en muchos built-ins. El código interactivo, dinámico o empaquetado también puede no tener fuente accesible.

getsourcelines() y comentarios

getsourcelines() devuelve líneas y número inicial.

lineas, inicio = inspect.getsourcelines(calcular_total)
print(inicio)
print("".join(lineas))

getcomments() busca comentarios anteriores a la definición. Prefiere docstrings y metadatos explícitos para contratos estables.

Firmas con signature()

signature() es la API recomendada para parámetros de callables.

from inspect import signature

sig = signature(calcular_total)
print(sig)
print(sig.return_annotation)

for nombre, parametro in sig.parameters.items():
    print(nombre, parametro.kind, parametro.default, parametro.annotation)

Entiende funciones, clases, métodos, functools.partial y muchos objetos callable.

Tipos de Parameter

Cada parámetro tiene un kind:

  • POSITIONAL_ONLY antes de /;
  • POSITIONAL_OR_KEYWORD para el caso común;
  • VAR_POSITIONAL para *args;
  • KEYWORD_ONLY después de *;
  • VAR_KEYWORD para **kwargs.
def ejemplo(a, /, b=2, *args, c, **kwargs):
    pass

for param in inspect.signature(ejemplo).parameters.values():
    print(param.name, param.kind.description)

Los frameworks pueden generar formularios, validadores y llamadas adaptadas.

Ausencia frente a None

Parameter.empty significa que no existe default o annotation. Es diferente de un valor explícito None.

param = inspect.signature(calcular_total).parameters["tasa"]
if param.default is inspect.Parameter.empty:
    print("Obligatorio")

Compara el marcador por identidad.

Validar llamadas con Signature.bind()

bind() asocia argumentos como una llamada real y lanza TypeError ante una combinación inválida.

sig = inspect.signature(calcular_total)

try:
    ligados = sig.bind(100, tasa=0.2)
    print(ligados.arguments)
except TypeError as error:
    print("Argumentos inválidos:", error)

Permite validar plugins y rutas antes de ejecutar.

bind_partial() y defaults

bind_partial() permite omitir argumentos obligatorios, como functools.partial().

parcial = sig.bind_partial(tasa=0.15)
print(parcial.arguments)

BoundArguments.apply_defaults() añade defaults, una tupla vacía para *args y un diccionario vacío para **kwargs.

ligados = sig.bind(100)
ligados.apply_defaults()
print(ligados.arguments)

Llamar con BoundArguments

Las propiedades args y kwargs permiten ejecutar después de validar o transformar.

ligados = sig.bind("100", tasa="0.2")
ligados.arguments["valor"] = float(ligados.arguments["valor"])
ligados.arguments["tasa"] = float(ligados.arguments["tasa"])
resultado = calcular_total(*ligados.args, **ligados.kwargs)

No uses annotations automáticamente como conversores; pueden ser objetos arbitrarios.

Annotations y riesgo de evaluación

signature() puede conservar annotations como cadenas o resolverlas. eval_str y el formato controlan el comportamiento. Evaluar cadenas puede ejecutar código arbitrario.

Para contenido no confiable, usa eval_str=False y formato textual. Python 3.14 integra annotation_format con annotationlib.Format.

Decorators y __wrapped__

Los decorators pueden ocultar nombre, docs y firma. functools.wraps() copia metadatos y crea __wrapped__.

from functools import wraps


def registrar(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        print("Llamando", func.__name__)
        return func(*args, **kwargs)
    return wrapper

@registrar
def sumar(a: int, b: int = 0) -> int:
    return a + b

print(inspect.signature(sumar))

La documentación oficial de functools explica cómo wraps conserva metadatos y acceso a la función original.

unwrap()

inspect.unwrap() sigue la cadena __wrapped__.

original = inspect.unwrap(sumar)
print(original.__name__)

Detecta ciclos y lanza ValueError. El callback stop permite detenerse en un wrapper.

follow_wrapped

signature() sigue wrappers por defecto. Usa follow_wrapped=False para analizar el wrapper.

print(inspect.signature(sumar, follow_wrapped=True))
print(inspect.signature(sumar, follow_wrapped=False))

Ayuda a depurar decorators que añaden comportamiento.

Modificar Signature y Parameter

Estos objetos son inmutables. Usa replace() o copy.replace().

sig = inspect.signature(sumar)
nueva = sig.replace(return_annotation="numero")
print(nueva)

Cambiar metadatos no cambia el comportamiento. Contrato e implementación deben mantenerse sincronizados.

Herencia y resolución de métodos

getmro() devuelve el orden de resolución.

class A: pass
class B(A): pass
class C(A): pass
class D(B, C): pass

print(inspect.getmro(D))

Explica qué implementación selecciona la herencia múltiple. getclasstree() organiza clases jerárquicamente.

Closures y variables externas

getclosurevars() informa nombres no locales, globales, built-ins y no resueltos.

factor = 10

def crear():
    adicional = 2
    def calcular(valor):
        return valor * factor + adicional
    return calcular

print(inspect.getclosurevars(crear()))

Es útil para depuración, pero expone referencias vivas. No registres secretos.

Estado de generators y coroutines

getgeneratorstate() informa si un generator está creado, ejecutándose, suspendido o cerrado.

def contador():
    yield 1
    yield 2

gen = contador()
print(inspect.getgeneratorstate(gen))
next(gen)
print(inspect.getgeneratorstate(gen))

Hay funciones equivalentes para coroutines y async generators.

Variables locales vivas

getgeneratorlocals(), getcoroutinelocals() y getasyncgenlocals() recuperan estado local mientras existe un frame.

Dependen de detalles del intérprete y pueden devolver mappings vacíos. No bases reglas de negocio en ellos.

Frames y pila de ejecución

currentframe(), stack(), trace(), getouterframes() y getinnerframes() ayudan a depuradores e informes.

frame = inspect.currentframe()
try:
    if frame is not None:
        info = inspect.getframeinfo(frame)
        print(info.filename, info.lineno, info.function)
finally:
    del frame

currentframe() puede devolver None en implementaciones sin soporte de frames Python.

Los frames pueden retener memoria

Los frames referencian variables locales y frames externos. Conservar uno puede crear ciclos y prolongar la vida de grafos grandes.

Elimina referencias en finally. Si guardas un frame temporalmente, llama a frame.clear() al terminar. Es esencial en servidores persistentes.

Introspección no equivale a API pública

Encontrar un atributo no significa que sea estable o soportado. Nombres con underscore, campos de code objects y detalles de frames pueden ser internos.

Los plugins deben declarar interfaces y versiones. Usa introspección para adaptación y diagnóstico, no para sustituir especificaciones.

Compatibilidad entre implementaciones

Algunos built-ins de CPython no proporcionan firma completa. Descriptors y code objects pueden variar entre intérpretes.

Captura TypeError, ValueError y OSError, ofrece alternativas y prueba todos los intérpretes soportados.

Ejemplo: registro de handlers

def registrar_handler(func):
    if not inspect.isfunction(func) and not inspect.ismethod(func):
        raise TypeError("El handler debe ser función o método")

    sig = inspect.signature(func)
    parametros = list(sig.parameters.values())
    if not parametros:
        raise TypeError("El handler debe recibir un evento")

    return {
        "callable": func,
        "firma": sig,
        "documentacion": inspect.getdoc(func) or "",
    }

Antes de ejecutar, usa bind() con el evento y dependencias disponibles.

Errores frecuentes

  • Usar getmembers() en objetos no confiables sin considerar properties.
  • Suponer que todo callable tiene fuente y firma.
  • Evaluar annotations de cadena sin revisar seguridad.
  • Crear decorators sin functools.wraps().
  • Confundir función de clase con método vinculado.
  • Conservar frames y crear ciclos.
  • Depender de detalles exclusivos de CPython.
  • Usar introspección en lugar de un contrato de plugins.

Buenas prácticas

  • Usa predicados is* para expresar intención.
  • Prefiere signature() a APIs antiguas.
  • Usa getmembers_static() para inspección pasiva.
  • Trata fuente y firma ausentes como casos normales.
  • Conserva __wrapped__ con wraps().
  • Evita evaluar annotations no confiables.
  • Libera frames en finally.
  • Prueba todas las implementaciones soportadas.

Conclusión

El módulo inspect en Python examina objetos vivos, clases, funciones, firmas, código fuente, closures, generators y pilas del intérprete. Permite construir documentación, registradores, depuradores y validadores adaptativos.

La introspección exige cuidado porque el acceso a atributos puede ejecutar descriptors, las annotations pueden implicar evaluación y los frames pueden retener grafos grandes. Al elegir APIs estáticas cuando corresponde, tolerar metadatos ausentes, preservar wrappers y liberar frames, aprovechas la flexibilidad dinámica de Python sin convertir el diagnóstico en efectos secundarios o fugas de memoria.

Compartilhe:

Facebook
WhatsApp
Twitter
LinkedIn

Contenido del artículo

    Artículos relacionados

    Módulo de memoria que representa referencias débiles y cachés en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    weakref en Python: referencias débiles

    Aprende weakref en Python para crear referencias débiles, cachés automáticas, observadores y finalizadores sin retener objetos en memoria.

    Ler mais

    Tempo de leitura: 9 minutos
    28/07/2026
    Icono de archivo ZIP para un artículo sobre zipfile en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    zipfile en Python: archivos ZIP seguros

    Aprende a crear, leer, validar y extraer archivos ZIP con zipfile en Python de forma predecible y segura.

    Ler mais

    Tempo de leitura: 5 minutos
    27/07/2026
    Grafo de dependencias y flujo de tareas en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    graphlib en Python: ordenación topológica

    Aprende graphlib en Python para ordenar dependencias, detectar ciclos y coordinar tareas independientes en paralelo.

    Ler mais

    Tempo de leitura: 6 minutos
    27/07/2026
    Código Python para contexto seguro en aplicaciones asíncronas
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    contextvars en Python: contexto seguro

    Aprende a usar contextvars en Python para aislar solicitudes, registros, hilos y tareas asyncio sin variables globales inseguras.

    Ler mais

    Tempo de leitura: 6 minutos
    26/07/2026
    Código Python con cached_property para guardar cálculos costosos
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    cached_property en Python: caché en objetos

    Aprende cached_property en Python para guardar cálculos costosos, invalidar valores y evitar cachés desactualizadas.

    Ler mais

    Tempo de leitura: 7 minutos
    25/07/2026
    Código Python con funciones especializadas por tipo
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    singledispatch en Python: funciones por tipo

    Aprende singledispatch en Python para crear funciones por tipo, reducir cadenas isinstance y organizar polimorfismo extensible con ejemplos.

    Ler mais

    Tempo de leitura: 7 minutos
    25/07/2026