Functions
def, *args / **kwargs, defaults, lambdas.
Python Functions
def declares a function. Indentation is the body.
def greet(name):
return f"Hello, {name}"
Functions are first-class values: pass them around, return them, store them in lists.
Default + keyword arguments
def fetch(url, timeout=30, retries=3):
...
fetch("https://x")
fetch("https://x", timeout=60)
fetch("https://x", retries=5, timeout=60) # any order via keyword
*args and **kwargs
*args collects positional, **kwargs collects keyword:
def log(*args, **kwargs):
print(args) # tuple of positional values
print(kwargs) # dict of keyword pairs
log(1, 2, level="info")
# (1, 2)
# {'level': 'info'}
The * and ** also work on the call site to unpack:
nums = [1, 2, 3]
print(*nums) # like print(1, 2, 3)
opts = {"timeout": 30, "retries": 3}
fetch("https://x", **opts)
Positional-only / keyword-only
A bare * in the parameter list forces keyword arguments after it:
def reset(user_id, *, hard=False):
...
reset(1, hard=True) # ok
reset(1, True) # TypeError — must use keyword
A bare / forces positional before it (3.8+):
def divide(a, b, /):
return a / b
divide(6, 2) # ok
divide(a=6, b=2) # TypeError
Lambda
A one-expression anonymous function. No statements allowed inside.
square = lambda x: x * x
sorted(users, key=lambda u: u.age)
If you need more than one line, write a regular def — even if you only use it once. Readability beats cleverness.
Default-argument footgun
Default values are evaluated once at definition time, not on every call. Mutable defaults are shared:
def append(item, target=[]): # BUG
target.append(item)
return target
append(1) # [1]
append(2) # [1, 2] — surprise!
Fix:
def append(item, target=None):
if target is None:
target = []
target.append(item)
return target
Docstrings
The first string in a function is its docstring. Tools (help, IDE hovers, Sphinx) pick it up:
def slugify(s):
"""Convert a string to a URL-friendly slug.
Lowercases, replaces non-alphanumerics with hyphens, strips
leading/trailing hyphens.
"""
...
Type hints
def fetch(url: str, timeout: int = 30) -> dict[str, str]:
...
Optional and ignored at runtime. Pair with mypy or pyright for actual checking.