Skip to content

Functions and Conditionals

Functions

A function in Python is defined with the def keyword, followed by a name, a parenthesized list of parameters, and a colon. The body is indented underneath. An optional return statement passes a value back to the caller.

Python
def slugify(name):
    cleaned = name.strip().lower()
    return cleaned.replace(" ", "-")


print(slugify("Payments API"))  # payments-api

If a function reaches the end without hitting a return, it returns None.

Default Parameter Values

Parameters can have defaults. If the caller doesn't pass a value, the default is used:

Python
def slugify(name, separator="-"):
    cleaned = name.strip().lower()
    return cleaned.replace(" ", separator)


print(slugify("Payments API"))  # payments-api
print(slugify("Payments API", "_"))  # payments_api

Defaults are evaluated once, when the function is defined. Don't use a mutable object like a list or dict as a default value. Use None and create the real default inside the function:

Python
def add_tag(name, tags=None):
    if tags is None:
        tags = []
    tags.append(name)
    return tags

Type Hints

Function parameters and return values can carry type hints. They don't change runtime behavior, but they document intent and let editors and type checkers catch bugs before the code runs.

Python
def slugify(name: str, separator: str = "-") -> str:
    cleaned = name.strip().lower()
    return cleaned.replace(" ", separator)

The : str after a parameter says "this should be a string." The -> str after the parentheses says "this function returns a string." We'll use type hints throughout the course because modern production code relies on them.

Keyword-only Arguments

A bare * in the parameter list marks every parameter that follows as keyword-only. Callers have to name them at the call site:

Python
def archive_project(name, *, notify=False, force=False):
    if force:
        print(f"Force-archiving {name}")
    if notify:
        print(f"Sent notification for {name}")
Python
archive_project("payments-api", notify=True)  # ok
archive_project("payments-api", notify=True, force=True)  # ok
archive_project("payments-api", True, True)  # TypeError

**kwargs for Arbitrary Keyword Arguments

**kwargs collects any extra keyword arguments into a dictionary. This is useful when a function passes options through to something else, or when the set of valid keys isn't fixed:

Python
def build_project(name, **kwargs):
    project = {"name": name, "slug": slugify(name)}
    project.update(kwargs)
    return project


print(
    build_project(
        "Payments API", description="Handles checkout", archived=False
    )
)

Inside the function, kwargs is a regular dict. The leading ** is only meaningful in the parameter list and at call sites, where it unpacks a dict into keyword arguments.

Conditional Statements

Use if, elif, and else for branching logic.

Info

Notice Python doesn't use curly braces, whitespace is used to denote nested blocks after the : of the if statement.

Python
status = "planned"

if status == "done":
    print("Task is complete")
elif status == "blocked":
    print("Task needs attention")
else:
    print("Task is still active")

This is useful whenever code should behave differently depending on the data it sees.

Example

Python
from datetime import date


def is_overdue(due_date, status):
    if due_date is None:
        return False

    if status == "done":
        return False

    return due_date < date.today()

Booleans and Truthiness

Truthiness

These values are falsy:

  • False
  • None
  • 0
  • "" (empty string)
  • empty lists, dictionaries, sets, and tuples

Booleans are defined by 0 and 1 under the hood.

Which means this is valid Python code.

Python
bool(1) + bool(1)

Important habit

Info

Truthiness is useful, but be careful when the distinction between None, "", and False matters. Later in the course, those differences change how how data is validated.