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.
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:
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:
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.
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:
def archive_project(name, *, notify=False, force=False):
if force:
print(f"Force-archiving {name}")
if notify:
print(f"Sent notification for {name}")
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:
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.
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
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:
FalseNone0""(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.
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.