inspect.signature: Inspect Function Parameters

Published on: August 29, 2026
Reading time: 5 minutes
Close-up view of a computer screen displaying code in a software development environment.

Functions, methods, classes, and callable objects expose a contract: positional parameters, keyword arguments, defaults, annotations, and return information. The inspect module makes that contract available at runtime through inspect.signature(). Web frameworks, dependency-injection containers, command-line generators, validators, decorators, documentation tools, and dynamic adapters all rely on this API.

This guide explains how to obtain signatures, interpret parameter kinds, bind arguments, apply defaults, preserve metadata through decorators, create custom signatures, and recognize the limits of runtime introspection.

Your first Signature object

from inspect import signature

def create_user(name: str, age: int = 18) -> dict[str, object]:
    return {"name": name, "age": age}

sig = signature(create_user)
print(sig)

The result is not merely formatted text. A Signature object contains ordered Parameter objects, a return annotation, and methods that validate the shape of a call.

Walking through parameters

for name, parameter in sig.parameters.items():
    print(name)
    print(parameter.kind)
    print(parameter.default)
    print(parameter.annotation)

parameters is an ordered mapping. Order matters because it reproduces the original declaration and Python’s real call rules.

The five Parameter kinds

Every parameter has a kind:

  • POSITIONAL_ONLY: may only be passed by position.
  • POSITIONAL_OR_KEYWORD: accepts position or name.
  • VAR_POSITIONAL: represents *args.
  • KEYWORD_ONLY: appears after * and requires a name.
  • VAR_KEYWORD: represents **kwargs.
def example(a, /, b, *args, c, **kwargs):
    ...

for p in signature(example).parameters.values():
    print(p.name, p.kind)

Frameworks that generate forms, routes, or commands must respect these categories. Turning every input into a keyword argument breaks positional-only parameters.

Missing defaults and annotations

When a parameter has no default or annotation, the API uses inspect.Parameter.empty.

from inspect import Parameter

for p in sig.parameters.values():
    if p.default is Parameter.empty:
        print(p.name, "is required")

Do not compare the default with None, because None may be a legitimate default value.

Return annotations

from inspect import Signature

if sig.return_annotation is not Signature.empty:
    print(sig.return_annotation)

A return annotation may be a class, generic expression, string, or another object. signature() does not replace typing.get_type_hints() when forward references must be resolved.

Validating calls with bind

bound = sig.bind("Ana", age=30)
print(bound.arguments)

bind() applies the same argument-assignment rules as the function call and raises TypeError for missing, duplicate, or unexpected arguments. It is valuable in adapters, dispatch systems, and wrappers.

Partial binding

partial = sig.bind_partial(name="Ana")

bind_partial() allows required arguments to remain absent. It is appropriate for functools.partial, builders, and configuration assembled in stages. Do not use it when a final call must already be complete.

Applying defaults

bound = sig.bind("Ana")
bound.apply_defaults()
print(bound.arguments)

Before apply_defaults(), the mapping contains only values supplied by the caller. Afterward, optional parameters receive their defaults, *args becomes an empty tuple, and **kwargs becomes an empty dictionary.

Working with BoundArguments

The object returned by binding exposes args, kwargs, and arguments. A normalized call can be forwarded directly:

result = create_user(*bound.args, **bound.kwargs)

Changing the ordered arguments mapping affects the derived properties. Do this carefully and validate types separately, because binding checks call structure rather than semantic correctness.

Methods and self

class Service:
    def run(self, task: str) -> None:
        ...

print(signature(Service.run))
print(signature(Service().run))

The unbound method signature includes self; the bound method usually does not. A framework must decide whether it is inspecting a class attribute or a callable obtained from an instance.

Callable instances

class Converter:
    def __call__(self, value: str, *, strict: bool = False) -> int:
        return int(value)

print(signature(Converter()))

signature() can inspect objects implementing __call__. This makes callable instances useful in pipelines, configurable dependencies, and strategy objects.

Classes and constructors

class User:
    def __init__(self, name: str, active: bool = True):
        ...

print(signature(User))

For classes, the visible call contract may come from metaclass __call__, __new__, or __init__. Custom metaclasses can therefore change what introspection reports.

Decorators can hide signatures

def log_calls(function):
    def wrapper(*args, **kwargs):
        print("call")
        return function(*args, **kwargs)
    return wrapper

Without additional metadata, the visible signature becomes (*args, **kwargs). Use functools.wraps to set __wrapped__ and preserve the original callable:

from functools import wraps

def log_calls(function):
    @wraps(function)
    def wrapper(*args, **kwargs):
        return function(*args, **kwargs)
    return wrapper

By default, signature() follows the __wrapped__ chain. The guide to Python decorators explains this pattern in depth.

Disabling wrapped traversal

signature(decorated_function, follow_wrapped=False)

Use follow_wrapped=False when you need to inspect the wrapper’s real runtime interface rather than the callable it wraps.

Custom signatures

Objects may expose __signature__. Frameworks use it to present a dynamically generated public interface. This changes introspection, not necessarily runtime behavior. A misleading custom signature can tell tools that a call is valid even when the wrapper rejects it.

Creating Parameter objects

from inspect import Parameter, Signature

parameter = Parameter(
    "limit",
    kind=Parameter.KEYWORD_ONLY,
    default=100,
    annotation=int,
)
new_signature = Signature([parameter], return_annotation=list)

Signature and Parameter objects are immutable. Build a new object or use replace() instead of mutating existing instances.

Replacing parts safely

updated = sig.replace(return_annotation=dict[str, object])

Parameter.replace() can change a name, default, kind, or annotation. The resulting sequence must still follow Python’s ordering rules: positional groups first, variadic parameters in valid positions, and required parameters before optional ones within a category.

String annotations

With postponed annotations, a signature may contain strings rather than resolved objects. Modern Python versions expose options for annotation evaluation and formatting, but portable framework code should separate structural inspection from type resolution.

from typing import get_type_hints

hints = get_type_hints(create_user, include_extras=True)

The guide to Python get_type_hints covers namespaces, forward references, and security.

Built-ins and extension functions

Many C-implemented functions provide signature metadata, but not all callables are introspectable. signature() may raise ValueError when no signature can be provided and TypeError when the object is not a supported callable.

try:
    sig = signature(obj)
except (TypeError, ValueError):
    sig = None

Dependency injection

A dependency container can inspect parameters, resolve each annotated dependency, and invoke the function with constructed values. However, annotations are not runtime validation. The container still needs policies for defaults, aliases, variadic parameters, scopes, and useful error messages.

Generating a CLI

Required parameters may become positional command-line arguments, keyword-only parameters may become options, and booleans may become flags. The signature alone does not provide friendly help text, constraints, examples, or environment-variable mapping. Annotated metadata can supplement that information.

Caching signatures

Repeated introspection on hot paths can add overhead. Since callable signatures are usually stable, frameworks often cache them by object identity. Invalidate the cache if decorators or plugin systems modify __signature__ dynamically.

Security and privacy

Structural inspection does not execute the callable, but resolving annotations can evaluate names. Do not resolve hints from untrusted plugin code without isolation. Also avoid exposing default values in generated documentation or logs when those defaults may contain tokens, paths, or credentials.

Common mistakes

  • Comparing defaults with None: use Parameter.empty.
  • Ignoring parameter kinds: positional-only and keyword-only rules are real.
  • Treating bind as type validation: it checks only call shape.
  • Losing metadata in decorators: apply functools.wraps.
  • Assuming every built-in is supported: catch TypeError and ValueError.
  • Publishing a fake __signature__: introspection and runtime behavior may diverge.

Complete example: configurable executor

from inspect import Parameter, signature
from typing import get_type_hints

def execute(function, values: dict[str, object]):
    sig = signature(function)
    hints = get_type_hints(function, include_extras=True)
    kwargs = {}

    for name, parameter in sig.parameters.items():
        if parameter.kind in {
            Parameter.VAR_POSITIONAL,
            Parameter.VAR_KEYWORD,
        }:
            continue

        if name in values:
            kwargs[name] = values[name]
        elif parameter.default is Parameter.empty:
            raise ValueError(f"missing value: {name}")

    bound = sig.bind(**kwargs)
    bound.apply_defaults()
    return function(*bound.args, **bound.kwargs)

The example normalizes the call and detects missing values. A production executor must also validate types, handle positional-only parameters, support conversion, and produce domain-specific diagnostics.

Conclusion

inspect.signature() turns a callable contract into structured, reliable objects. With Signature, Parameter, and BoundArguments, you can validate call shape, generate interfaces, adapt wrappers, and preserve decorator metadata without manually parsing source code.

The official inspect.signature documentation defines the full API. Combine it with get_type_hints(), respect every parameter kind, and keep introspection separate from type validation and execution.

Share:

Facebook
WhatsApp
Twitter
LinkedIn

Article content

    Related articles

    Vivid close-up of a python resting among autumn leaves, showcasing its intricate patterns.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    get_origin and get_args: Inspect Generic Types

    Learn Python get_origin and get_args to inspect generics, unions, Annotated, Literal, aliases, and runtime type metadata safely.

    Ler mais

    Tempo de leitura: 6 minutos
    29/08/2026
    Detailed shot of a Jungle Carpet Python (Morelia spilota cheynei) in its natural habitat.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python LiteralString: Trusted Strings

    Learn Python LiteralString to restrict SQL, templates, and commands to trusted strings and reduce injection risks with static analysis.

    Ler mais

    Tempo de leitura: 5 minutos
    29/08/2026
    A detailed view of computer programming code on a screen, showcasing software development.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python dataclass_transform: Generated Classes

    Learn Python dataclass_transform to type decorators, base classes, and metaclasses that generate fields, __init__, and methods.

    Ler mais

    Tempo de leitura: 5 minutos
    29/08/2026
    A close-up shot showcasing the intricate scales of a snake, highlighting texture and color.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python TypeVarTuple: Variadic Generics

    Learn Python TypeVarTuple to preserve heterogeneous tuples, model dimensions, and build generics with variable type parameters.

    Ler mais

    Tempo de leitura: 4 minutos
    29/08/2026
    A developer typing code on a laptop with a Python book beside in an office.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    assert_type and reveal_type: Test Type Inference

    Learn Python assert_type and reveal_type to inspect inference, test typed APIs, and prevent static typing regressions.

    Ler mais

    Tempo de leitura: 4 minutos
    29/08/2026
    High-angle view of woman coding on a laptop, with a Python book nearby. Ideal for programming and tech content.
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    Python get_type_hints: Resolve Annotations

    Learn Python get_type_hints to resolve forward references, preserve Annotated metadata, and inspect functions and classes safely.

    Ler mais

    Tempo de leitura: 5 minutos
    29/08/2026