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

    SQLite database and Python code for selective iterdump export
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    SQLite iterdump: Create Selective Dumps in Python

    Learn sqlite3.Connection.iterdump filters for selective SQLite dumps, dependency checks, validation, and safer restoration.

    Ler mais

    Tempo de leitura: 4 minutos
    21/10/2026
    Code and files representing Python data compression
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    compression.zstd: Fast Zstandard in Python

    Learn Python compression.zstd for fast file and data compression, streaming, integrity checks, and safe decompression.

    Ler mais

    Tempo de leitura: 7 minutos
    21/10/2026
    Configuration file and code structure for Python ConfigParser
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    ConfigParser: Read INI Files Without Sections

    Learn Python ConfigParser unnamed sections with validation, typed values, compatibility, layered settings, and safe INI writing.

    Ler mais

    Tempo de leitura: 5 minutos
    20/10/2026
    Binary data and computer memory representing Python memoryview
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    memoryview count and index: Search Without Copies

    Learn Python memoryview count and index to search values in zero-copy buffers and validate binary data efficiently.

    Ler mais

    Tempo de leitura: 5 minutos
    20/10/2026
    Developer working with Python code and data validation
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    map strict: Detect Misaligned Iterables

    Learn Python map strict to validate iterables, prevent silent truncation, and detect misaligned data early.

    Ler mais

    Tempo de leitura: 4 minutos
    19/10/2026
    Organized folders representing Python files and directories
    Advanced Python
    Foto de perfil de Leandro Hirt da Academify

    pathlib.Path.info: Speed Up Directory Scans

    Learn pathlib.Path.info in Python to reuse file metadata, speed up directory scans, and avoid stale cache assumptions.

    Ler mais

    Tempo de leitura: 6 minutos
    19/10/2026