Introduction
Functions are the primary way to organize and reuse code in Python. This lesson covers everything from basic definitions and parameter types to type hints and Python 3.14's deferred annotations that eliminate the need for quoted forward references.
Key Concepts
defstatement: Defines a named, reusable block of code.- Positional and keyword arguments: Two ways to pass values to functions.
*args/**kwargs: Collect variable numbers of positional or keyword arguments.- Positional-only (
/) and keyword-only (*) parameters: Enforce how callers pass arguments. - Type hints: Optional annotations that document expected types and enable static analysis.
- Deferred annotations (PEP 649): Python 3.14 feature that removes the need for quoted forward references.
Real World Context
Well-designed function signatures are the contract between your code and its callers. In large codebases, keyword-only parameters prevent accidental argument ordering bugs, and type hints catch misuse before runtime. Libraries like FastAPI and Pydantic rely heavily on function signatures and type hints to auto-generate documentation and validate input.
Deep Dive
Basic Syntax
pythondef function_name(parameters): """Docstring describing the function.""" # function body return value
Positional and Keyword Arguments
pythondef greet(name, greeting="Hello"): return f"{greeting}, {name}!" greet("Alice") # "Hello, Alice!" greet("Bob", "Hi") # "Hi, Bob!" greet(greeting="Hey", name="Eve") # "Hey, Eve!"
*args and **kwargs
pythondef flexible(*args, **kwargs): print(f"Positional: {args}") print(f"Keyword: {kwargs}") flexible(1, 2, 3, name="Alice", age=30) # Positional: (1, 2, 3) # Keyword: {'name': 'Alice', 'age': 30}
Keyword-Only Arguments (after *)
pythondef configure(*, debug=False, verbose=False): # debug and verbose must be passed as keywords pass configure(debug=True) # OK configure(True) # TypeError!
Positional-Only Arguments (before /)
pythondef pow(x, y, /): # x and y must be positional return x ** y pow(2, 10) # OK: 1024 pow(x=2, y=10) # TypeError!
Type Hints
pythondef add(a: int, b: int) -> int: return a + b def greet(name: str, times: int = 1) -> list[str]: return [f"Hello, {name}!"] * times
Deferred Annotations (Python 3.14+)
In Python 3.14, annotations are no longer eagerly evaluated (PEP 649). This means forward references work without quotes:
python# Before 3.14 -- needed quotes for forward references def create() -> "MyClass": return MyClass() # Python 3.14+ -- no quotes needed def create() -> MyClass: return MyClass() class MyClass: pass
Common Pitfalls
- Using a mutable default argument --
def add_item(item, lst=[]):shares the same list across all calls. UseNoneas the default and create a new list inside the function:if lst is None: lst = []. - Confusing positional-only and keyword-only separators --
/ends positional-only parameters;*begins keyword-only parameters. Mixing them up causes confusing TypeErrors. - Ignoring type hints in team projects -- Type hints are not enforced at runtime, but skipping them means you lose the benefits of static analysis tools like mypy and IDE autocomplete.
Best Practices
- Use keyword-only arguments for boolean flags --
def run(*, verbose=False)prevents callers from accidentally passingTrueas a positional argument to the wrong parameter. - Always write a docstring for public functions -- A one-line docstring is enough for simple functions; use Google or NumPy style for complex ones.
Summary
- Functions are defined with
defand can accept positional, keyword,*args, and**kwargsparameters. - Use
/for positional-only and*for keyword-only to control how callers pass arguments. - Type hints document expected types and enable static analysis without runtime overhead.
- Python 3.14's deferred annotations (PEP 649) eliminate the need for quoted forward references.
- Never use mutable default arguments; use
Noneand create fresh objects inside the function.
Code Examples
python
# Combining parameter types
def api_call(
endpoint, # Positional or keyword
/, # Everything before is positional-only
*args, # Extra positional args
timeout=30, # Keyword argument with default
**kwargs # Extra keyword args
):
pass
# Modern type hints
def process[T](items: list[T]) -> list[T]:
return [item for item in items if item]