Typorithm · medium

Function signatures as a public API

pythonlibrariespackagingindependent-practice
Saved on this device
Full lesson · 4 learning steps Open any exercise for focused practice
1

Step 1 of 4 · intro card

Function signatures as a public API

0%

Concept and contract

Design positional-only and keyword-only boundaries, and distinguish binding errors from calculations.

A function signature is part of its public contract. The slash makes amount positional-only; the star makes factor keyword-only. A caller supplies the value by position and labels the scaling option. The default factor applies only when that keyword is omitted.

The example deliberately tries two call shapes with the same numeric inputs. One returns a product; the other records a binding rejection. Renaming an internal positional-only parameter need not break callers that already follow this contract, while changing the keyword name would change their accepted interface.

Mechanism and worked example

Read the nested scale function before following the dispatcher.

Read the nested scale function before following the dispatcher. A positional request passes value as the first argument and factor as a named option, producing twelve for four and three. The keyword request names amount and is rejected before the function body executes.

Only that deliberately invalid call catches TypeError. The function does not broadly convert every error into a successful product. The outer unknown-mode branch remains a ValueError, keeping an unsupported operation distinct from a malformed call to an otherwise known operation.

Complete worked example

python

def inspect_example(value, mode):
    def scale(amount, /, *, factor=2):
        return amount * factor
    if mode == 'positional':
        return scale(value, factor=3)
    if mode == 'keyword':
        try:
            return scale(amount=value, factor=3)
        except TypeError:
            return 'positional-only rejected'
    raise ValueError('unknown mode')
Reasoning
Read the nested scale function before following the dispatcher. A positional request passes value as the first argument and factor as a named option, producing twelve for four and three. The keyword request names amount and is rejected before the function body executes.
Download the reusable Python and packaging computer lab ↗

Counterexamples and common mistakes

The controlled bug replaces the explicit multiplier three with four.

The controlled bug replaces the explicit multiplier three with four. The program still parses and the signature is unchanged, but a valid positional request now produces sixteen instead of twelve. Binding constraints cannot establish a correct business calculation.

A different tempting mistake would remove the slash to make the invalid call pass. That changes the API rather than fixing the caller. Tests should cover both accepted and rejected call shapes, including zero, because a single happy-path multiplication cannot protect the signature contract by itself.

Controlled bug: the executable contract rejects this version

python

def inspect_example(value, mode):
    def scale(amount, /, *, factor=2):
        return amount * factor
    if mode == 'positional':
        return scale(value, factor=4)
    if mode == 'keyword':
        try:
            return scale(amount=value, factor=4)
        except TypeError:
            return 'positional-only rejected'
    raise ValueError('unknown mode')
Reasoning
The controlled bug replaces the explicit multiplier three with four. The program still parses and the signature is unchanged, but a valid positional request now produces sixteen instead of twelve. Binding constraints cannot establish a correct business calculation.
Download the reusable Python and packaging computer lab ↗

Independent practice

Keep the nested signature and change the practice multiplier to five.

Keep the nested signature and change the practice multiplier to five. Locate both dispatch branches, then decide which observable outcomes must actually change. The valid positional case changes its result, but the keyword case should still fail during argument binding.

Use the three declared cases before inventing more. Four should become twenty, zero must remain zero, and the rejection label should remain identical. Avoid changing the return type or replacing the calculation with a constant: either would disguise the relationship between input values and the public call contract.

Step 1: Make positional calls use factor five while retaining the positional-only boundary. Step 2: Run the accepted baseline, edit the exercise, then check its independent solution cases; preserve caller inputs and the documented exception behavior.

Practice starter: solve before reading the solution

python

def inspect_example(value, mode):
    def scale(amount, /, *, factor=2):
        return amount * factor
    if mode == 'positional':
        return scale(value, factor=3)
    if mode == 'keyword':
        try:
            return scale(amount=value, factor=3)
        except TypeError:
            return 'positional-only rejected'
    raise ValueError('unknown mode')

# Task: Make positional calls use factor five while retaining the positional-only boundary.
# Practice contract: Make positional calls use factor five while retaining the positional-only boundary.
Reasoning
Make positional calls use factor five while retaining the positional-only boundary.
Download the reusable Python and packaging computer lab ↗
  • Hint 1: Keep the nested signature and change the practice multiplier to five. Locate both dispatch branches, then decide which observable outcomes must actually change. The valid positional case changes its result, but the keyword case should still fail during argument binding.
  • Hint 2: Use the three declared cases before inventing more. Four should become twenty, zero must remain zero, and the rejection label should remain identical. Avoid changing the return type or replacing the calculation with a constant: either would disguise the relationship between input values and the public call contract.

Solution, tests and trade-offs

The solution changes the chosen factor without relaxing the slash or star.

The solution changes the chosen factor without relaxing the slash or star. Acceptance cases therefore test two independent properties: the new arithmetic decision and the existing restrictions on callers. Keeping these concerns separate makes an API change reviewable.

A published library should describe its call style in examples and documentation. Runtime inspection can expose a signature, but it cannot decide whether an incompatible change is acceptable for existing consumers. Reserve defaults for deliberate behavior, and add a compatibility test when another package depends on the old spelling or call shape.

Practice solution and acceptance checks

python

def inspect_example(value, mode):
    def scale(amount, /, *, factor=2):
        return amount * factor
    if mode == 'positional':
        return scale(value, factor=5)
    if mode == 'keyword':
        try:
            return scale(amount=value, factor=5)
        except TypeError:
            return 'positional-only rejected'
    raise ValueError('unknown mode')
Reasoning
The solution changes the chosen factor without relaxing the slash or star. Acceptance cases therefore test two independent properties: the new arithmetic decision and the existing restrictions on callers. Keeping these concerns separate makes an API change reviewable.
Download the reusable Python and packaging computer lab ↗

Interview reasoning and next step

Explain when a positional-only argument is useful: for example, a value whose internal name should not become a caller keyword.

Explain when a positional-only argument is useful: for example, a value whose internal name should not become a caller keyword. Then explain why a configurable factor benefits from a keyword label. Neither syntax is automatically better for every function.

For a stretch, add another keyword-only option with a default and write a compatibility matrix of old calls and new calls. Check unknown keywords and duplicate bindings explicitly. Keep the experiment local; the lesson is about accepting a deliberate interface, not catching every TypeError and continuing with a guessed value.

Optional stretch — after the core task works: For a stretch, add another keyword-only option with a default and write a compatibility matrix of old calls and new calls. Check unknown keywords and duplicate bindings explicitly. Keep the experiment local; the lesson is about accepting a deliberate interface, not catching every TypeError and continuing with a guessed value.

Computer practice: https://applydjinn.com/typorithm/python-engineering-lab-20261107.zip. Use an isolated Python 3.13 environment, install requirements.txt, then run verify.py. Edit exercises/pye-signatures.py and run --practice pye-signatures. Fixture files are created in fresh temporary directories for every case. Packaging checks build real local artifacts, install the exact wheel without an index and run outside the source tree. No package is published to PyPI. The app includes static examples, prepared results and questions; it does not run Python or provide an editor.

Design positional-only and keyword-only boundaries, and distinguish binding errors from calculations.