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

    Componentes de servidor que representan intérpretes Python aislados ejecutándose en paralelo
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    InterpreterPoolExecutor: paralelismo real en Python

    Aprende InterpreterPoolExecutor en Python para tareas CPU-bound, intérpretes aislados, paralelismo real y concurrencia segura.

    Ler mais

    Tempo de leitura: 6 minutos
    13/09/2026
    Microprocesador que representa las CPU disponibles para un proceso Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    os.process_cpu_count: cuenta CPUs disponibles

    Aprende os.process_cpu_count en Python para dimensionar workers según las CPU disponibles para el proceso.

    Ler mais

    Tempo de leitura: 4 minutos
    12/09/2026
    Portátil con código digital que representa datos BLOB en SQLite
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    sqlite3.Blob: lee BLOBs sin cargar todo en memoria

    Aprende sqlite3.Blob en Python para leer y escribir BLOBs por partes, reducir memoria y manejar datos binarios en SQLite.

    Ler mais

    Tempo de leitura: 5 minutos
    12/09/2026
    Análisis estadístico para random.binomialvariate en Python
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    random.binomialvariate: simula resultados binomiales

    Aprende random.binomialvariate en Python para simular éxitos, validar probabilidades y analizar escenarios binomiales con ejemplos.

    Ler mais

    Tempo de leitura: 5 minutos
    11/09/2026
    Código Python y análisis de firmas de funciones
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    inspect.signature.bind: valida argumentos de funciones

    Aprende inspect.signature.bind en Python para validar argumentos, aplicar valores predeterminados y crear APIs dinámicas seguras.

    Ler mais

    Tempo de leitura: 5 minutos
    11/09/2026
    Código Python para limpieza segura de directorios con shutil.rmtree
    Python Avanzado
    Foto de perfil de Leandro Hirt da Academify

    shutil.rmtree onexc: maneja errores al borrar carpetas

    Aprende shutil.rmtree con onexc en Python para eliminar directorios, tratar permisos, registrar fallos y crear limpiezas seguras.

    Ler mais

    Tempo de leitura: 7 minutos
    10/09/2026