Skip to content
Python Basics: Check Call Shapes with inspect.signature

Python Basics: Check Call Shapes with inspect.signature

October 23, 2026

A command adapter often receives positional arguments and named options before it calls a trusted function. inspect.signature can show the accepted shape and bind proposed arguments without running that function. Use it to produce clearer errors at an integration boundary, not to validate business data.

Prepare a scratch directory

These standalone examples were tested on Linux with Python 3.12.14. Use Python 3.8 or newer because the examples include positional-only parameters. Use Bash, a text editor, and the standard mktemp, rm, and rmdir utilities. No third-party packages are required. Run without -O so the example assertions remain enabled.

Create a fresh directory and keep using the same terminal:

start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1

Save each named file below as UTF-8. Do not name your script after a standard-library module.

1. Distinguish parameter kinds

Save as describe_report_signature.py:

import inspect

def render_report(destination, /, title="Summary", *, width=72):
    return destination, title, width

signature = inspect.signature(render_report)
print("Signature:", signature)
for name, parameter in signature.parameters.items():
    print(f"{name}: {parameter.kind.name}")
assert signature.parameters["width"].kind is inspect.Parameter.KEYWORD_ONLY

Run it:

python3 -B describe_report_signature.py

Output:

Signature: (destination, /, title='Summary', *, width=72)
destination: POSITIONAL_ONLY
title: POSITIONAL_OR_KEYWORD
width: KEYWORD_ONLY

The slash makes destination positional-only. The star makes width keyword-only. The signature preserves these distinctions, unlike a plain list of parameter names. Defaults are visible too, but an annotation or default does not enforce a runtime type check.

2. Bind values and fill defaults before calling

Save as bind_report_arguments.py:

import inspect

calls = []
def render_report(destination, /, *, width=72):
    calls.append(destination)
    return f"{destination}: width={width}"

signature = inspect.signature(render_report)
bound = signature.bind("preview.txt")
bound.apply_defaults()
assert calls == []
print("Bound:", dict(bound.arguments))
print("Calls before invocation:", len(calls))
print(render_report(*bound.args, **bound.kwargs))
assert calls == ["preview.txt"]

Run it:

python3 -B bind_report_arguments.py

Output:

Bound: {'destination': 'preview.txt', 'width': 72}
Calls before invocation: 0
preview.txt: width=72

bind checks required arguments and their positions without invoking render_report. apply_defaults makes omitted defaults explicit. BoundArguments exposes args and kwargs in a form that can be passed back to the function. Inspecting a signature is not a promise that a later call will succeed: the function can still reject values or raise during its work.

3. Catch missing and unexpected options

Save as reject_invalid_call_shapes.py:

import inspect

def format_label(name, *, uppercase=False):
    return name.upper() if uppercase else name

signature = inspect.signature(format_label)
cases = [
    ("missing", (), {}),
    ("unknown", ("draft",), {"color": "blue"}),
    ("valid", ("draft",), {"uppercase": True}),
]
for label, arguments, options in cases:
    try:
        bound = signature.bind(*arguments, **options)
    except TypeError:
        print(f"{label}: rejected")
    else:
        print(f"{label}: {format_label(*bound.args, **bound.kwargs)}")
partial = signature.bind_partial()
assert partial.arguments == {}
print("Partial binding allows missing name:", True)

Run it:

python3 -B reject_invalid_call_shapes.py

Output:

missing: rejected
unknown: rejected
valid: DRAFT
Partial binding allows missing name: True

bind_partial intentionally permits missing required arguments, which is useful when assembling a call in stages. Use bind when the call should already be complete. Catching a binding error separately avoids accidentally labeling a TypeError raised inside the called function as a bad argument list.

Apply this pattern to trusted callables with normal signatures. Some builtins cannot provide introspection metadata, and signature can raise ValueError or TypeError. A custom signature or decorator can also affect the reported interface. Do not use successful binding as a permission check, schema validator, or guarantee that executing an unknown callable is safe.

Clean up

In the same terminal, remove only the named sources, return to the original directory, and remove the empty scratch directory:

rm -- describe_report_signature.py bind_report_arguments.py reject_invalid_call_shapes.py
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dir

If rmdir reports remaining files, inspect them instead of deleting unknown contents.

References

Acknowledgements

This post was created with assistance from AI.

Last updated on