TL;DR: Using parameter kinds and type annotation goes a long way toward creating clear, robust programs and APIs. Best practices:
- Use keywords for parameters that have meaningful and stable names.
- Use keyword-only for options and booleans
- Use positional-only where parameter names would have little to no meaning or where you might want to change parameter names later.
- Annotate to give callers with an explicit recipe for your API.
Types of parameters
-
Positional-or-Keyword: By default, Python parameters can be passed by position or by name. For
def my_func(param1: str, param2: str, param3: list):, Python first binds any argument with explicit keywords to its corresponding parameter, then binds any remaining positional arguments in order. These calls are equivalent:
my_func('a', 'b', [1, 2, 3])
my_func(param3=[1, 2, 3], param2='b', param1='a')
my_func('a', param2='b', param3=[1, 2, 3])Note: Positional arguments must come before keyword args.
my_func(param2='b', param1='a', [1, 2, 3])is invalid. -
Positional-Only: Parameters before a
/in the signature are positional-only PEP 570. Use when a name adds little to no meaning, or when you want freedom to rename later. Examples:
sorted(iterable, /, *, key=None, reverse=False)→iterableis positional-only.
pow(x, y, z=None, /)→ all three are positional-only. -
Keyword-Only: Parameters after a bare
*must be passed by name (PEP 3102. Great for optional parameters and booleans because they enforce self-documenting call sites. -
Optional: A parameter becomes optional when you give it a default. When omitted at the call site, an optional parameter will be assigned the default value, which should correspond to expected behavior. Examples:
reverse=Falseinsorted();ascending=Trueinpandas.DataFrame.sort_values();startrow = 0inpandas.DataFrame.to_excel(). -
Variable Length Arguments: Var-positional (
*args) collects extra positional values; Var-keyword (**kwargs) collects extra named options. Often used to forward to another API. Rules of thumb
-Prefer explicit parameters when you can.
-Use*args/**kwargswhen you truly need "any number of" values/options or you're writing wrappers.
Annotations
Annotations provide users with explicit clues about which types are expected for any parameter, and which types a function will return. The syntax is name: Type for parameters and -> Type for the return. They're hints, not runtime enforcement.
Example:
def get_one_path(data_dir: Path, ptrn: str, *, ext: str | None = None) -> Path:
data_dir: Path→ expects a path-like object.ptrn: str→ expects a string object.ext: str | None = None→ optional keyword-only; expects a string orNone; defaults toNone.-> Path→ the function returns aPath.
tags: #Python, #tools-and-languages, #instructive