Parameters in Python: Positional vs Keyword Enforcement

Parameter Kinds in Python: Positional vs Keyword Enforcement

– Standard Python parameter behavior:
By default, parameters in Python can be passed either by position (func(10)) or by keyword (func(x=10)).
While flexible, this default behavior can lead to ambiguous call sites or prevent library authors from renaming internal arguments without breaking public APIs.

– The role of / and * markers:
/ (Slash): Enforces positional-only arguments for all parameters defined before it.
* (Asterisk): Enforces keyword-only arguments for all parameters defined after it.
– Parameters placed between / and * can be passed using either style.

Positional-Only Parameters with /

– Why use positional-only arguments (PEP 570):
Introduced in Python 3.8, the / marker prevents callers from relying on parameter names.
Use cases include:
– When parameter names have no semantic value (e.g., mathematical inputs like x, y).
– When you want the freedom to rename parameters in future releases without breaking user code.
– Matching the behavior of built-in C functions (like abs() or len()).

– Example: Enforcing positional arguments:

# Everything before '/' MUST be passed by position
def calculate_tax(amount: float, rate: float, /) -> float:
    return amount * rate
 
# 1. Valid call:
tax = calculate_tax(100.0, 0.20)
 
# 2. Invalid call (raises TypeError: calculate_tax() got some positional-only arguments passed as keyword arguments):
# tax = calculate_tax(amount=100.0, rate=0.20)

Keyword-Only Parameters with *

– Why use keyword-only arguments (PEP 3102):
A bare * forces callers to explicitly name all parameters that follow.
Use cases include:
Boolean flags: Calling create_user("Alice", True, False) is unreadable, whereas create_user("Alice", is_admin=True, notify=False) is explicit and self-documenting.
– Preventing silent bugs caused by passing arguments in the wrong order.

– Example: Enforcing explicit keyword arguments:

# Everything after '*' MUST be explicitly named at the call site
def create_button(label: str, *, is_primary: bool = False, disabled: bool = False) -> dict:
    return {"label": label, "is_primary": is_primary, "disabled": disabled}
 
# 1. Valid call:
btn = create_button("Submit", is_primary=True)
 
# 2. Invalid call (raises TypeError: create_button() takes 1 positional argument but 2 were given):
# btn = create_button("Submit", True)

Combining Both: The Complete Signature Blueprint

– The full anatomy of a Python signature:
You can combine both markers in the same function to strictly control each argument segment:
def func(pos_only, /, standard, *, kw_only): ...

– Real-world example: Data export function:

from typing import Any
 
def export_data(
    data: list[dict[str, Any]], # Positional-only (obvious input payload)
    /,
    target_format: str = "json", # Standard (can be passed positionally or by name)
    *,
    compress: bool = False,      # Keyword-only (boolean flag)
    overwrite: bool = True       # Keyword-only (boolean flag)
) -> str:
    return f"Exported {len(data)} items to {target_format} (compressed={compress})"
 
# --- Valid calls ---
# 1. Standard call using positional payload and keyword options:
export_data([{"id": 1}], "csv", compress=True)
 
# 2. Explicitly naming the format parameter:
export_data([{"id": 1}], target_format="xml", overwrite=False)
 
# --- Invalid calls ---
# 1. Passing positional-only 'data' with a keyword (TypeError):
# export_data(data=[{"id": 1}], target_format="json")
 
# 2. Passing keyword-only 'compress' positionally (TypeError):
# export_data([{"id": 1}], "json", True)
Ce contenu a été publié dans Non classé. Vous pouvez le mettre en favoris avec ce permalien.

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *