Funciones, métodos, clases y objetos invocables exponen un contrato: parámetros posicionales, argumentos por nombre, valores predeterminados, anotaciones y retorno. El módulo inspect permite leer ese contrato en runtime mediante inspect.signature(). Frameworks web, contenedores de dependencias, generadores de CLI, validadores, decorators, documentación y adapters dinámicos utilizan esta API.
Esta guía explica cómo obtener firmas, interpretar tipos de parámetros, vincular argumentos, aplicar defaults, conservar metadatos en decorators, crear firmas personalizadas y reconocer los límites de la introspección.
Primera firma
from inspect import signature
def crear_usuario(nombre: str, edad: int = 18) -> dict[str, object]:
return {"nombre": nombre, "edad": edad}
sig = signature(crear_usuario)
print(sig)
El resultado no es solamente texto formateado. Un objeto Signature contiene parámetros ordenados, anotación de retorno y métodos que validan la forma de una llamada.
Recorrer parámetros
for nombre, parametro in sig.parameters.items():
print(nombre)
print(parametro.kind)
print(parametro.default)
print(parametro.annotation)
parameters es un mapping ordenado. El orden importa porque reproduce la declaración original y las reglas reales de invocación de Python.
Los cinco Parameter kinds
POSITIONAL_ONLY: solo acepta posición.POSITIONAL_OR_KEYWORD: acepta posición o nombre.VAR_POSITIONAL: representa*args.KEYWORD_ONLY: aparece después de*y exige nombre.VAR_KEYWORD: representa**kwargs.
def ejemplo(a, /, b, *args, c, **kwargs):
...
for p in signature(ejemplo).parameters.values():
print(p.name, p.kind)
Un framework que genera formularios, rutas o comandos debe respetar estas categorías. Convertir todo en argumentos keyword rompe parámetros positional-only.
Defaults y anotaciones ausentes
Cuando no existe default o anotación, la API usa inspect.Parameter.empty.
from inspect import Parameter
for p in sig.parameters.values():
if p.default is Parameter.empty:
print(p.name, "es obligatorio")
No compares con None, porque None puede ser un valor predeterminado legítimo.
Anotación de retorno
from inspect import Signature
if sig.return_annotation is not Signature.empty:
print(sig.return_annotation)
La anotación puede ser una clase, una expresión genérica, una string u otro objeto. signature() no sustituye typing.get_type_hints() cuando hay que resolver referencias futuras.
Validar llamadas con bind
vinculados = sig.bind("Ana", edad=30)
print(vinculados.arguments)
bind() aplica las mismas reglas de asignación que una llamada real y genera TypeError ante argumentos ausentes, duplicados o inesperados. Es útil en adapters, wrappers y sistemas de despacho.
Binding parcial
parcial = sig.bind_partial(nombre="Ana")
bind_partial() permite que falten parámetros obligatorios. Es apropiado para functools.partial, builders y configuraciones construidas por etapas. No lo uses cuando la llamada final ya debe estar completa.
Aplicar defaults
vinculados = sig.bind("Ana")
vinculados.apply_defaults()
print(vinculados.arguments)
Antes de apply_defaults(), el mapping contiene solo valores enviados. Después, los parámetros opcionales reciben sus defaults, *args se convierte en tupla vacía y **kwargs en diccionario vacío.
BoundArguments
El objeto vinculado expone args, kwargs y arguments. Una llamada normalizada puede reenviarse directamente:
resultado = crear_usuario(*vinculados.args, **vinculados.kwargs)
Modificar arguments cambia las propiedades derivadas. Hazlo con cuidado y valida tipos por separado, porque bind comprueba la estructura, no el significado.
Métodos y self
class Servicio:
def ejecutar(self, tarea: str) -> None:
...
print(signature(Servicio.ejecutar))
print(signature(Servicio().ejecutar))
La firma del método no vinculado incluye self; la del método vinculado normalmente no. Un framework debe decidir si inspecciona el atributo de clase o el callable obtenido de una instancia.
Instancias invocables
class Conversor:
def __call__(self, valor: str, *, estricto: bool = False) -> int:
return int(valor)
print(signature(Conversor()))
signature() puede inspeccionar objetos con __call__. Esto es útil en pipelines, dependencias configurables y objetos estrategia.
Clases y constructores
class Usuario:
def __init__(self, nombre: str, activo: bool = True):
...
print(signature(Usuario))
En clases, el contrato visible puede provenir de __call__ de la metaclase, __new__ o __init__. Una metaclase personalizada puede cambiar el resultado.
Decorators que ocultan firmas
def registrar(funcion):
def wrapper(*args, **kwargs):
print("llamada")
return funcion(*args, **kwargs)
return wrapper
Sin metadatos adicionales, la firma visible se convierte en (*args, **kwargs). Usa functools.wraps para definir __wrapped__:
from functools import wraps
def registrar(funcion):
@wraps(funcion)
def wrapper(*args, **kwargs):
return funcion(*args, **kwargs)
return wrapper
Por defecto, signature() sigue la cadena __wrapped__. Consulta la guía de decorators en Python.
follow_wrapped
signature(funcion_decorada, follow_wrapped=False)
Usa follow_wrapped=False cuando necesites inspeccionar la interfaz real del wrapper en lugar de la función original.
Firmas personalizadas
Los objetos pueden exponer __signature__. Los frameworks lo utilizan para presentar una interfaz pública generada dinámicamente. Esto modifica la introspección, no necesariamente el comportamiento. Una firma falsa puede indicar a las herramientas que una llamada es válida cuando el wrapper la rechaza.
Crear Parameter
from inspect import Parameter, Signature
parametro = Parameter(
"limite",
kind=Parameter.KEYWORD_ONLY,
default=100,
annotation=int,
)
nueva_firma = Signature([parametro], return_annotation=list)
Signature y Parameter son inmutables. Construye nuevos objetos o usa replace().
Modificar con replace
actualizada = sig.replace(return_annotation=dict[str, object])
Parameter.replace() puede cambiar nombre, default, kind o anotación. La secuencia resultante debe respetar el orden válido de Python: grupos posicionales primero y parámetros obligatorios antes de opcionales dentro de su categoría.
Anotaciones como strings
Con anotaciones diferidas, la firma puede contener strings. Versiones modernas ofrecen opciones de evaluación y formato, pero el código portable debe separar introspección estructural de resolución de tipos.
from typing import get_type_hints
hints = get_type_hints(crear_usuario, include_extras=True)
La guía de get_type_hints en Python explica namespaces, referencias futuras y seguridad.
Built-ins y extensiones
Muchas funciones implementadas en C exponen metadatos de firma, pero no todos los callables son introspectables. signature() puede generar ValueError cuando no existe firma y TypeError cuando el objeto no es invocable o no está soportado.
try:
sig = signature(objeto)
except (TypeError, ValueError):
sig = None
Inyección de dependencias
Un contenedor puede inspeccionar parámetros, resolver cada dependencia anotada y llamar a la función. Sin embargo, las anotaciones no validan en runtime. El contenedor necesita políticas para defaults, alias, parámetros variádicos, scopes y mensajes de error.
Generar una CLI
Parámetros obligatorios pueden convertirse en argumentos posicionales, keyword-only en opciones y bool en flags. La firma no incluye ayuda amigable, restricciones ni ejemplos. Metadatos de Annotated pueden complementar esa información.
Cache
La introspección repetida en rutas calientes puede tener coste. Como las firmas suelen ser estables, los frameworks las guardan por identidad del objeto. Invalida el cache si decorators o plugins modifican __signature__ dinámicamente.
Seguridad y privacidad
La inspección estructural no ejecuta el callable, pero resolver anotaciones puede evaluar nombres. No resuelvas hints de plugins no confiables sin aislamiento. Tampoco publiques defaults en documentación o logs si pueden contener tokens, rutas o credenciales.
Errores comunes
- Comparar default con None: usa
Parameter.empty. - Ignorar parameter kind: positional-only y keyword-only tienen reglas reales.
- Confundir bind con validación de tipos: solo comprueba la forma.
- Perder metadatos en decorators: aplica
functools.wraps. - Suponer soporte en todo built-in: captura TypeError y ValueError.
- Publicar un __signature__ engañoso: runtime e introspección pueden divergir.
Ejemplo completo: ejecutor configurable
from inspect import Parameter, signature
from typing import get_type_hints
def ejecutar(funcion, valores: dict[str, object]):
sig = signature(funcion)
hints = get_type_hints(funcion, include_extras=True)
kwargs = {}
for nombre, parametro in sig.parameters.items():
if parametro.kind in {
Parameter.VAR_POSITIONAL,
Parameter.VAR_KEYWORD,
}:
continue
if nombre in valores:
kwargs[nombre] = valores[nombre]
elif parametro.default is Parameter.empty:
raise ValueError(f"falta valor: {nombre}")
vinculados = sig.bind(**kwargs)
vinculados.apply_defaults()
return funcion(*vinculados.args, **vinculados.kwargs)
El ejemplo normaliza la llamada y detecta ausencias. Un ejecutor real debe validar tipos, manejar positional-only, convertir entradas y producir diagnósticos del dominio.
Conclusión
inspect.signature() convierte el contrato de un callable en objetos estructurados y fiables. Con Signature, Parameter y BoundArguments puedes validar la forma de llamadas, generar interfaces y preservar decorators sin analizar código fuente manualmente.
La documentación oficial de inspect.signature define la API completa. Combínala con get_type_hints(), respeta cada parameter kind y separa introspección, validación y ejecución.







