Dataslope logoDataslope

Functions

def, parameters, defaults, *args, **kwargs, return values, and type hints

Functions package code so it can be reused, named, and tested. Every program is a tree of function calls. The keyword is def, the body is indented, and a function ends as soon as its indentation does.

Where are functions used?

Functions are the fundamental building block of modern programs:

  • Every API — whether it's a REST endpoint, a library method, or a CLI command — is ultimately a function call with inputs and outputs.
  • Testing — Unit tests verify one function at a time. Your test suite is only as good as your functions are isolated.
  • Functional programming — Pure functions (same input → same output, no side effects) make code easier to reason about, parallelize, and debug.
  • The call stack — Your entire program is a tree of function calls. When you see a traceback, each line is a function that called the next.

Think of every import requests; requests.get(url) or df.groupby("category").mean() — each is a function doing one job well, composed with others.

Etymology: functions vs mathematical functions

The term function comes from mathematics: f(x) = y means "given input x, produce output y."

In math, functions are pure:

  • f(3) always returns the same value.
  • f doesn't modify anything; it just computes.

Python functions can behave this way (and should, when possible):

def square(x):
    return x * x

But Python functions can also have side effects (printing, writing files, mutating arguments, maintaining state). This makes them more powerful but also harder to test:

def log_and_square(x):
    print(f"computing square of {x}")  # side effect: I/O
    return x * x

When you hear "functional programming," it means prefer pure functions: no side effects, same input → same output, every time.

Anatomy of a function call

A function is defined once with def, then called many times. Each call creates a new local scope, binds arguments to parameters, executes the body, and returns a value (or None if no return is hit).

Defining a function

Code Block
Python 3.13.2

A function that does not explicitly return returns None.

Code Block
Python 3.13.2

Docstrings are your friend

The triple-quoted string right after def is the docstring. help(greet) will show it. Write them for any function someone else (or future you) will call.

Parameters and arguments

Python distinguishes between positional and keyword arguments.

Code Block
Python 3.13.2

When to use keyword arguments

Use positional for the 1–2 obvious arguments (open(filename), range(10)). Use keyword for everything else, especially booleans and numbers whose meaning isn't clear from position alone: retry(url, timeout=30, retries=3).

Default values

Defaults are evaluated once, when the function is defined (not when it's called). A mutable default value is the most famous Python footgun:

Code Block
Python 3.13.2

Mutable default argument gotcha

Never use a mutable object ([], {}, custom class) as a default. Use None as a sentinel and create the object inside the function.

Code Block
Python 3.13.2

*args and **kwargs

*args captures any extra positional arguments as a tuple; **kwargs captures any extra keyword arguments as a dict.

Code Block
Python 3.13.2

When calling a function, * and ** unpack iterables/dicts back into arguments:

Code Block
Python 3.13.2

Why *args, **kwargs?

The names args and kwargs are just convention; the * and ** are the syntax. You can write *numbers, **options if you prefer. But *args, **kwargs is so ubiquitous that everyone recognizes it instantly.

Positional-only and keyword-only parameters

Modern Python lets you control exactly how callers may pass each argument:

def func(pos_only, /, pos_or_kw, *, kw_only):
    ...
  • Parameters before / are positional-only.
  • Parameters after * are keyword-only.
  • Parameters between / and * can be passed either way.
Code Block
Python 3.13.2

When to use / and *

Use positional-only (/) when the parameter name is an implementation detail and you want freedom to rename it later (e.g., len(obj) — who cares what the parameter is called?). Use keyword-only (*) when you have many parameters or when the meaning isn't obvious from position alone (open(file, *, encoding="utf-8")).

Return values

A return statement exits the function immediately and sends a value back to the caller.

Code Block
Python 3.13.2

You can return multiple values by separating them with commas. Python packs them into a tuple:

Code Block
Python 3.13.2

Use early return to avoid deep nesting:

Code Block
Python 3.13.2

Lambdas

A lambda is an anonymous, single-expression function. They are useful as key= arguments to sorted, min, max, and similar higher-order functions.

Code Block
Python 3.13.2

lambda vs def

For anything beyond a one-liner, prefer def. Named functions show up in tracebacks; lambdas do not. A named function also gets a docstring and is easier to test in isolation.

Type hints

Type hints (PEP 484+) are optional annotations. Python itself does not enforce them at runtime; tools like mypy, pyright, and editors use them to catch bugs and power autocompletion.

Code Block
Python 3.13.2

You can put them on plain variables too:

count: int = 0
names: list[str] = []
cache: dict[str, int] = {}

Type hints as documentation

Type hints are living documentation that stays in sync with the code (unlike a comment, which can drift). They also unlock powerful IDE features: jump-to-definition, find-all-references, refactor-rename, etc.

First-class functions

In Python, functions are first-class objects: you can pass them as arguments, store them in data structures, and return them from other functions.

Code Block
Python 3.13.2

This is the foundation of decorators, callbacks, and many design patterns.

Higher-order functions: map, filter, sorted

These built-ins take a function and an iterable.

Code Block
Python 3.13.2

Comprehensions vs map/filter

Comprehensions are usually clearer and faster in Python. Use map/filter when you already have a named function and applying it is a one-liner (map(str.upper, words)). Otherwise, reach for a list comprehension.

Real-world patterns

Pure functions (no side effects) are easier to test and parallelize:

# pure: always returns same output for same input
def tax(amount, rate):
    return amount * rate

# impure: prints (side effect)
def log_tax(amount, rate):
    result = amount * rate
    print(f"tax on {amount} at {rate}: {result}")
    return result

Higher-order functions (functions that take or return functions) are everywhere in Python:

from functools import lru_cache

@lru_cache(maxsize=128)
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

Here, lru_cache is a decorator — a function that takes a function and returns a wrapped version with caching.

Challenges

Challenge
Python 3.13.2
Compose two functions

Define a function compose(f, g) that takes two single-argument functions and returns a new function h such that h(x) == f(g(x)) for any x.

Challenge
Python 3.13.2
Flexible greeter

Define greet_all(*names, greeting="Hello") that returns a single string greeting each name on its own line, in the form "<greeting>, <name>!". If no names are passed, return the empty string.

Example:

greet_all("Ada", "Grace", greeting="Hi")
# -> "Hi, Ada!\nHi, Grace!"
Challenge
Python 3.13.2
Fix the mutable default

The function below has a mutable default argument bug. Fix it by using None as a sentinel and creating a new list inside the function when needed.

Challenge
Python 3.13.2
Recursive factorial

Define a function factorial(n) that computes n! recursively. factorial(0) should return 1.

Multiple choice questions

QuestionSelect one

What does this print?

def f(x, items=[]):
  items.append(x)
  return items

print(f(1))
print(f(2))

[1]\n[2]

[1]\n[1, 2]

[1, 2]\n[1, 2]

A TypeError

QuestionSelect one

Given def f(a, b, /, c, *, d): ..., which call is valid?

f(1, 2, 3, d=4)

f(a=1, b=2, c=3, d=4)

f(1, 2, 3, 4)

f(1, b=2, c=3, d=4)

QuestionSelect one

What is the return value of a function that has no return statement?

0

None

An empty string ""

It raises a SyntaxError

QuestionSelect one

Which statement about lambda is true?

lambda functions can contain multiple statements.

lambda functions are anonymous and show up as <lambda> in tracebacks.

lambda functions are faster than def functions.

lambda functions cannot take arguments.

QuestionSelect one

What does unpacking with * do when calling a function?

It captures extra positional arguments into a tuple.

It expands an iterable into separate positional arguments.

It makes the argument keyword-only.

It creates a pointer to the iterable.

QuestionSelect one

Why are type hints useful in Python?

They make the code run faster.

They are enforced by the Python interpreter at runtime.

They help tools like mypy catch bugs and improve IDE autocompletion.

They are required for all functions in Python 3.10+.

Next: where Python looks for names when you use them.

On this page