Glossary

This glossary explains common Python terms in plain English. Each entry is a mini-reference: a one-line definition, a short explanation, a runnable example, the everyday tools that go with the term, and links to the lessons where you’ll use it.

Browse by category below, or jump straight to a term. New to programming? Start with Variable, String, List, Dictionary, and Function. They underpin almost everything else.

Start with these terms

Python basics

The building blocks every program is made of.

Variable A name that refers to a stored value, so you can reuse it by name.

You create a variable with the assignment operator =: the name on the left starts referring to the value on the right. The same name can be reassigned at any time, and it can hold any type of value, text, a number, a list, and so on.

Names may contain letters, digits, and underscores, cannot start with a digit, and are case-sensitive, age and Age are two different variables.

Example
age = 30          # bind the name "age" to the value 30
name = "Ada"      # a name can hold any type of value
age = age + 1     # reassign: "age" now refers to 31
print(name, age)
Output
Ada 31

Where this shows up in real Python

Variables are everywhere: storing user input, counting progress in a loop, holding an API response, or remembering a filename to write to later.

Commonly used Variable tools

A variable isn’t a class with methods, but a few built-ins inspect what a name points to:

  • type(value), show what kind of object a name points to
  • isinstance(value, int), check whether it points to a given type
  • del name, remove a name so it no longer points to anything

Related lessons

Related terms

Syntax The grammar rules for how Python code must be written.

Syntax is the set of grammar rules every Python program must follow: a colon after an if, for, or def line; consistent indentation for the block underneath; and matching quotes and brackets. Break a rule and Python reports a SyntaxError and refuses to run the file at all, nothing executes until it's fixed.

Example
# Correct syntax: a colon, then an indented block
if 3 > 2:
    print("valid Python")

# Missing the colon would raise, before anything runs:
#   SyntaxError: expected ':'
Output
valid Python

Where this shows up in real Python

Syntax governs every line you write. A syntax error stops the program before it runs at all, unlike a runtime error that happens mid-execution.

Official documentation: Python Tutorial: Syntax Errors

Related lessons

Related terms

Data types

The kinds of values Python works with.

String A piece of text, written between quotes, e.g. "hello".

A string is text wrapped in single or double quotes, both styles are equivalent. You can join strings with +, read one character by position with text[0], take a slice with text[1:4], and call built-in methods such as .upper() or .replace().

Strings are immutable: a method never changes the original, it returns a brand-new string.

Example
greeting = "hello"
print(greeting.upper())       # methods return a NEW string
print(greeting[0])            # index: the first character
print(f"{greeting}, world!")  # f-string formatting
Output
HELLO
h
hello, world!

Where this shows up in real Python

Strings show up whenever you touch text: cleaning user input, building filenames, formatting a report line, or reading a row from a CSV.

Commonly used String tools

str is a built-in type with many methods, the ones you’ll use most:

  • .upper() / .lower(), return an upper/lowercased copy
  • .strip(), remove surrounding whitespace
  • .replace(old, new), swap one substring for another
  • .split(sep), break text into a list of parts
  • ' '.join(parts), join a list of strings back together
  • .startswith(x) / .endswith(x), test how the text begins or ends
  • f'{value}', f-strings drop values straight into text

Related lessons

Related terms

Integer A whole number with no decimal point, e.g. 42.

An integer (int) is a whole number, positive or negative, with no fractional part. Python integers have unlimited size. They never overflow, so you can compute enormous numbers exactly. Use // for floor (whole-number) division and % for the remainder.

Example
count = 42
print(count + 8)        # ordinary arithmetic
print(7 // 2)           # floor division -> 3
print(2 ** 100)         # exact, no overflow
print(int("100"))       # turn a string of digits into an int
Output
50
3
1267650600228229401496703205376
100

Where this shows up in real Python

Whole numbers count things, index into sequences, drive loop ranges, and store money in whole cents to avoid rounding errors.

Commonly used Integer tools

  • int('42'), turn a string of digits into an int
  • a // b, floor (whole-number) division
  • a % b, the remainder
  • abs(n), distance from zero
  • 2 ** 10, exact powers, no overflow
  • divmod(a, b), quotient and remainder together

Related lessons

Related terms

Float A number with a decimal point, e.g. 3.14.

A float is a number with a decimal point, used for measurements and any value that isn't whole. The division operator / always produces a float, even for something like 10 / 2. Floats are stored in binary, so a few decimals can't be represented exactly and you may see a tiny rounding error.

Example
price = 3.14
print(price * 2)              # 6.28
print(10 / 2)                 # / always gives a float -> 5.0
print(0.1 + 0.2)              # a tiny rounding error appears
print(round(0.1 + 0.2, 2))    # round for display
Output
6.28
5.0
0.30000000000000004
0.3

Where this shows up in real Python

Floats handle anything with a fractional part: averages, prices, measurements, and percentages.

Commonly used Float tools

  • float('3.14'), parse a decimal string
  • round(x, 2), round to a number of decimal places
  • abs(x), drop the sign
  • f'{x:.2f}', format to a fixed number of decimals
  • import math, math.floor, math.ceil, math.sqrt, …

Related lessons

Related terms

Boolean A value that is either True or False.

A boolean (bool) holds one of just two values: True or False. Comparisons such as 5 > 3 evaluate to a boolean, and booleans drive if statements and while loops. Combine them with and, or, and not.

Python also treats other values as "truthy" or "falsy": 0, 0.0, an empty string, an empty list [], and None all count as false inside a condition.

Example
is_open = True
print(5 > 3)            # a comparison produces a boolean
print(is_open and False)
print(not is_open)
print(bool(""))         # an empty string counts as False
Output
True
False
False
False

Where this shows up in real Python

Booleans drive every decision: the condition in an if or while, validating input, and toggling features on or off.

Commonly used Boolean tools

  • and / or / not, combine and invert conditions
  • == != < > <= >=, comparisons that produce True/False
  • any(items) / all(items), is any/are all of them truthy
  • bool(x), see how a value is judged truthy or falsy

Related lessons

List An ordered, changeable collection of items written in square brackets.

A list is an ordered, changeable collection written in square brackets, like [1, 2, 3]. Items keep their order, can be of mixed types, and are reached by position starting at 0, items[0] is the first and items[-1] the last. Lists are mutable: you can append, insert, replace, and remove items after creating them.

Example
fruits = ["apple", "pear", "plum"]
fruits.append("kiwi")   # lists can grow
print(fruits[0])        # first item
print(fruits[-1])       # last item
print(len(fruits))      # how many items
Output
apple
kiwi
4

Where this shows up in real Python

Lists collect things in order: rows read from a file, a queue of tasks, or results you build up as a loop runs.

Commonly used List tools

list is a built-in type, the methods you’ll reach for most:

  • .append(x), add one item to the end
  • .extend(items), add several items
  • .insert(i, x), add at a position
  • .remove(x), delete the first matching item
  • .pop(i), remove and return an item
  • .sort() / sorted(seq), order in place / return a sorted copy
  • len(seq), seq[1:3], x in seq, length, slice, membership

Official documentation: Python Tutorial: More on Lists

Related lessons

Related terms

Tuple An ordered, unchangeable sequence of values, written in parentheses, e.g. (3, 4).

A tuple is like a list, but immutable, once created, its items cannot be changed. You write one in parentheses (or just commas), index it like a list, and unpack it into several variables at once. Tuples are ideal for a fixed group of values, like an (x, y) coordinate or the several results a function returns.

Example
point = (3, 4)          # a 2-item tuple
print(point[0])         # index like a list
x, y = point            # unpack into two variables
print(x, y)
# point[0] = 9          # would raise: tuples cannot be changed
Output
3
3 4

Where this shows up in real Python

Tuples show up as coordinates, database rows, dictionary keys, and any time a function returns several values at once.

Commonly used Tuple tools

  • (1, 2, 3), create a tuple with parentheses
  • x, y = point, unpack into variables
  • point[0], index like a list
  • len(point), item in point, length and membership
  • tuple([1, 2]), convert a list to a tuple

Official documentation: Python Library Reference: Tuples

Related lessons

Related terms

Set An unordered collection of unique items, written in braces, e.g. {1, 2, 3}.

A set stores unique values, no duplicates, with no fixed order. It is built for fast membership tests (x in s) and set math: union, intersection, and difference. Create one with braces or set(...). Note that {} makes an empty dictionary, so use set() for an empty set.

Example
emails = ["[email protected]", "[email protected]", "[email protected]"]
unique = set(emails)         # duplicates dropped
print(len(unique))
print("[email protected]" in unique)   # fast membership test
Output
2
True

Where this shows up in real Python

Sets are perfect for removing duplicates, testing membership quickly, and comparing two collections, who is in list A but not list B.

Commonly used Set tools

  • .add(x), add an item
  • .discard(x), remove without error if missing
  • a | b, union, items in either
  • a & b, intersection, items in both
  • a - b, difference, in a but not b
  • set(items), drop duplicates from a list

Official documentation: Python Library Reference: Set Types

Related lessons

Related terms

Dictionary A collection of key-value pairs for looking up values by key.

A dictionary stores data as key/value pairs inside curly braces, like {'name': 'Sam'}. Instead of a numeric position you look things up by key. Keys must be unique, so assigning to an existing key overwrites its value, and .get() returns a fallback you choose when a key might be missing.

Example
ages = {"Sam": 30, "Ada": 36}
print(ages["Ada"])         # look up a value by its key
ages["Lee"] = 25           # add a new pair
ages["Sam"] = 31           # reassign an existing key
print(ages.get("Max", 0))  # a default when the key is absent
Output
36
0

Where this shows up in real Python

Dictionaries map keys to values: counting how often things occur, holding configuration, or modeling JSON-like records you look up by name.

Commonly used Dictionary tools

dict is a built-in type with its own methods:

  • .get(key, default), read safely instead of raising KeyError
  • .keys() / .values(), view all keys or all values
  • .items(), loop over key/value pairs together
  • .setdefault(key, default), read a key, inserting a default if missing
  • .update(other), merge another dict’s pairs in
  • .pop(key, default), remove a key and return its value

Official documentation: Python Tutorial: Dictionaries

Related lessons

Related terms

Control flow

How a program decides what to do and repeats work.

Conditional Code that runs only when a condition is true (if / elif / else).

A conditional chooses which code to run based on a Boolean test. if runs its block when the condition is true; optional elif branches test further conditions; else catches everything left over. Only the first matching branch runs.

Example
score = 72
if score >= 90:
    grade = "A"
elif score >= 60:
    grade = "pass"
else:
    grade = "fail"
print(grade)
Output
pass

Where this shows up in real Python

Conditionals are behind every decision a program makes: validating input, choosing a code path, handling edge cases.

Conditional tools

  • if / elif / else, branch on one or more conditions
  • and / or / not, combine conditions
  • x if cond else y, a one-line conditional expression
  • ==, in, <, >, the tests that produce True/False

Official documentation: Python Tutorial: if Statements

Related lessons

Related terms

if statement The statement that runs a block of code only when a condition is true.

An if statement is the most common conditional: it tests a Boolean condition and runs the indented block beneath it only when that condition is True. Add elif for extra cases and else for the fallback.

Example
temperature = 30
if temperature > 25:
    print("Warm")
else:
    print("Cool")
Output
Warm

Where this shows up in real Python

If statements guard risky actions, pick between options, and check results throughout every real program.

if / elif / else

  • if condition:, run a block conditionally
  • elif other:, test another case
  • else:, the fallback branch
  • if x and y:, combine conditions

Official documentation: Python Tutorial: if Statements

Related lessons

Loop Code that repeats, either over items (for) or while a condition holds (while).

A loop runs the same block of code more than once. A for loop repeats once per item in a sequence (a list, string, range, and so on), binding each item to a variable in turn. A while loop keeps going as long as a condition stays True. Inside either, break stops the loop early and continue skips to the next round.

Example
for n in [1, 2, 3]:       # once per item
    print(n)

count = 0
while count < 2:          # repeat while the condition holds
    count += 1
print("done", count)
Output
1
2
3
done 2

Where this shows up in real Python

Loops do the repetitive work: processing each file, row, or item; retrying until something succeeds; or building a list one piece at a time.

Commonly used Loop tools

  • range(n), loop a fixed number of times
  • enumerate(seq), loop with an index
  • zip(a, b), loop over two sequences together
  • break / continue, stop early / skip to the next item
  • [x for x in seq], a comprehension, a loop that builds a list

Official documentation: Python Tutorial: for Statements

Related lessons

for loop A loop that runs once for each item in a sequence.

A for loop walks through a sequence — a list, string, range, dictionary, or any iterable, binding each item to a variable in turn and running its body once per item. It is the go-to loop when you know what you are iterating over.

Example
for fruit in ["apple", "pear", "plum"]:
    print(fruit.upper())

for i in range(3):        # 0, 1, 2
    print("row", i)
Output
APPLE
PEAR
PLUM
row 0
row 1
row 2

Where this shows up in real Python

For loops process each file in a folder, each row in a CSV, each result from an API, anywhere you handle a collection one item at a time.

Common for-loop tools

  • range(n), loop a fixed number of times
  • enumerate(seq), loop with an index
  • zip(a, b), loop two sequences together
  • break / continue, stop early / skip an item
  • for k, v in d.items(), loop a dictionary’s pairs

Official documentation: Python Tutorial: for Statements

Related lessons

Related terms

while loop A loop that keeps repeating as long as a condition stays true.

A while loop repeats its body over and over as long as a condition is True, checking the condition before each round. Use it when you do not know in advance how many times to repeat, waiting for valid input, retrying, or running until a total is reached. Something inside must eventually make the condition false, or the loop never ends.

Example
count = 0
while count < 3:        # check before each round
    print("tick", count)
    count += 1          # move toward the stop condition
Output
tick 0
tick 1
tick 2

Where this shows up in real Python

While loops drive menus, retry-until-success logic, and any process that runs until a condition changes rather than a fixed number of times.

Common while-loop tools

  • while condition:, repeat while it stays True
  • break, leave the loop immediately
  • continue, skip to the next check
  • while True: … break, loop until you decide to stop

Related lessons

Related terms

Exception An error raised while a program runs, which you can catch and handle.

An exception is an error that happens while a program runs, as opposed to a syntax error, which is caught before it starts. When something goes wrong (dividing by zero, a missing key, bad input), Python raises an exception that stops the program with a traceback unless you catch it. Wrap risky code in try / except to handle the problem and keep running.

Example
try:
    number = int("not a number")   # this raises ValueError
except ValueError:
    number = 0                     # handle it instead of crashing
print(number)
Output
0

Where this shows up in real Python

Exceptions show up wherever things can go wrong: bad user input, a missing file, a failed network call. Handling them keeps a script from crashing.

Commonly used Exception tools

  • try / except, run risky code and catch failures
  • except ValueError, catch one specific kind of error
  • else / finally, run on success / always run (cleanup)
  • raise, signal an error yourself
  • ValueError, KeyError, FileNotFoundError, common built-in types

Related lessons

try / except A block that runs risky code and catches exceptions instead of crashing.

try / except is how Python handles exceptions. Code that might fail goes in the try block; if it raises an error, the matching except block runs instead of the program crashing. Add else for code to run when nothing failed, and finally for cleanup that always runs.

Example
input_value = "oops"
try:
    age = int(input_value)     # raises ValueError
except ValueError:
    age = 0                    # handle it
finally:
    print("done")              # always runs
print(age)
Output
done
0

Where this shows up in real Python

You will wrap file reads, network calls, and type conversions in try/except so one bad input does not take down the whole script.

try / except tools

  • try: / except:, run risky code and catch failures
  • except ValueError as e, catch a type and inspect it
  • except (A, B), catch several types at once
  • else:, run only if no error
  • finally:, always run (cleanup)
  • raise, re-raise or signal an error

Official documentation: Python Tutorial: Handling Exceptions

Related lessons

Traceback The report Python prints when an error stops a program, showing the error and the path of calls that reached it.

A traceback is Python telling you exactly what went wrong and how it got there. It is printed from the outside in: the first call at the top, the place the error actually happened at the bottom, and the error itself on the very last line.

That ordering is why tracebacks look intimidating and why the reading rule is simple. Read the last line first: it names the exception type and its message. Then scan upwards for the deepest frame in a file you wrote; frames below that usually sit inside a library and are rarely where the fault is.

Each middle entry is a frame: a file, a line number, and the function that was running. Together they are the chain of calls that led to the failure, which is what makes a traceback more useful than a plain error message.

Example
import traceback

def parse_year(text):
    return int(text)

def load(rows):
    years = []
    for row in rows:
        years.append(parse_year(row))
    return years

try:
    load(["2024", "not-a-year"])
except ValueError as err:
    frames = traceback.extract_tb(err.__traceback__)
    print("error:  ", f"{type(err).__name__}: {err}")
    print("chain:  ", " -> ".join(frame.name for frame in frames))
    print("deepest:", frames[-1].name)
Output
error:   ValueError: invalid literal for int() with base 10: 'not-a-year'
chain:   <module> -> load -> parse_year
deepest: parse_year

Functions

Reusable blocks of logic, and the tools built on them.

Function A reusable, named block of code that can take inputs and return a result.

You define a function with the def keyword, an optional list of parameters in parentheses, and an indented body. Calling the function runs that body, and a return statement hands a value back to whoever called it. A function with no return hands back None.

Functions let you write a piece of logic once and reuse it with different inputs, which keeps programs short and easier to fix.

Example
def greet(name):           # "name" is a parameter
    return "Hello, " + name + "!"

message = greet("Sam")     # call it with an argument
print(message)
Output
Hello, Sam!

Where this shows up in real Python

Functions organize scripts, remove repetition, and make larger programs easy to read and test one piece at a time.

Commonly used Function tools

Patterns you’ll reach for constantly once you write your own:

  • def greet(name='friend'), default values make an argument optional
  • *args, **kwargs, accept any number of positional/keyword arguments
  • def total(x: int) -> int, type hints document inputs and output
  • return, hand a value back (no return means None)

Official documentation: Python Tutorial: Defining Functions

Related lessons

Parameter A named variable in a function definition that receives an input value.

Parameters are the names listed in parentheses when you def a function. They act as placeholders: when the function is called, each parameter is filled with the matching argument. A parameter can have a default value, which makes it optional.

Example
def greet(name, greeting="Hello"):   # two parameters; greeting has a default
    return greeting + ", " + name

print(greet("Sam"))
print(greet("Sam", "Hi"))
Output
Hello, Sam
Hi, Sam

Where this shows up in real Python

Every function you write or call uses parameters, from a script’s settings to a web route’s inputs.

Commonly used Parameter tools

  • def f(a, b), positional parameters
  • def f(a, b=10), a default value makes it optional
  • def f(*args), accept any number of positional values
  • def f(**kwargs), accept any number of keyword values
  • def f(a: int), type-hint a parameter

Official documentation: Python Glossary: parameter

Related lessons

Argument The actual value you pass to a function when you call it.

An argument is the real value handed to a function at call time, filling one of its parameters. You can pass arguments positionally (by order) or as keyword arguments (by name), which is clearer when there are several.

Example
def make_user(name, admin=False):
    return {"name": name, "admin": admin}

print(make_user("Ada"))                # positional
print(make_user("Bo", admin=True))     # keyword argument
Output
{'name': 'Ada', 'admin': False}
{'name': 'Bo', 'admin': True}

Where this shows up in real Python

Arguments feed data into every function call, a filename to open, a URL to fetch, options for a command-line tool.

Commonly used Argument tools

  • f(1, 2), positional arguments, matched by order
  • f(name='Sam'), keyword argument, matched by name
  • f(*my_list), unpack a list into positional arguments
  • f(**my_dict), unpack a dict into keyword arguments

Official documentation: Python Glossary: argument

Related lessons

Return value The value a function hands back to its caller with the return statement.

A return statement ends a function and sends a value back to whoever called it, so you can store or use the result. A function with no return hands back None. Returning is different from printing: print() shows text on screen, while return gives a value back to your program.

Example
def total(prices):
    return sum(prices)      # hand the result back

bill = total([3, 5, 2])     # store the returned value
print(bill)
Output
10

Where this shows up in real Python

Return values let functions build on each other, one function’s result becomes another’s input, which is how larger programs are assembled.

Working with return values

  • return value, hand one value back
  • return a, b, return several values as a tuple
  • return, exit early, handing back None
  • result = f(), capture what a function returns

Related lessons

Lambda A small anonymous function written in one line with the lambda keyword.

A lambda is a tiny function with no name, written inline: lambda x: x * 2. It lists its arguments before the colon and returns the single expression after it, no def or return needed.

Lambdas shine as a quick argument to functions like sorted(), map(), or filter() when a full named function would be overkill. For anything longer, use def.

Example
double = lambda x: x * 2
print(double(5))

pairs = [("a", 3), ("b", 1), ("c", 2)]
pairs.sort(key=lambda pair: pair[1])   # sort by the number
print(pairs)
Output
10
[('b', 1), ('c', 2), ('a', 3)]

Where this shows up in real Python

Lambdas shine as short, throwaway functions passed to other functions. Most often the key= for sorting or finding a max.

Commonly used Lambda tools

  • lambda x: x * 2, an inline, unnamed function
  • sorted(items, key=lambda r: r['age']), sort by a computed key
  • max(items, key=lambda x: len(x)), pick by a computed value

Official documentation: Python Tutorial: Lambda Expressions

Related terms

Decorator A function that wraps another function to add behavior, applied with the @name syntax above a def.

A decorator is written as @something on the line above a function definition. It takes the function below it and returns a modified version, a clean way to add behavior without changing the function's own code.

You'll meet decorators most often when using a web framework: Flask's @app.route("/") is a decorator that registers a function as a route.

Example
def shout(func):
    def wrapper(name):
        return func(name).upper()
    return wrapper

@shout                       # wrap greet with shout
def greet(name):
    return f"hello {name}"

print(greet("ada"))
Output
HELLO ADA

Where this shows up in real Python

Decorators add behavior, logging, timing, access checks, web routing, without editing the function they wrap.

Commonly used Decorator tools

  • @something, apply a decorator to the function below
  • functools.wraps, keep the wrapped function’s name/help
  • @property, a built-in decorator for class attributes
  • @app.route('/'), Flask routing is a decorator

Official documentation: Python Glossary: decorator

Related terms

Generator A function that produces a sequence of values lazily, one at a time, using yield instead of return.

A generator is a function that uses yield instead of return. Each yield hands back one value and pauses the function, keeping its variables; the next request resumes it right where it left off. So a generator produces a sequence one item at a time rather than building the whole thing up front.

That makes generators lazy (nothing runs until you iterate) and great for streaming large files or long sequences with very little memory. They’re also a kind of iterator, and they’re one-shot: once consumed, you make a new one to iterate again.

Example
def count_up_to(limit):
    n = 1
    while n <= limit:
        yield n
        n += 1

for number in count_up_to(3):
    print(number)
Output
1
2
3

Where this shows up in real Python

Generators stream values one at a time, so you can process a huge file or an endless sequence without loading it all into memory.

Commonly used Generator tools

  • yield value, hand back one value and pause
  • next(gen), pull the next value
  • (x for x in seq), a generator expression
  • import itertools, ready-made generator building blocks

Official documentation: Python Tutorial: Generators

Related terms

Iterator An object you can step through one item at a time with next(), until it is exhausted.

An iterator is anything you can pull values from one at a time with next(), until it runs out. Every for loop uses one under the hood: it calls next() repeatedly and stops when the iterator is exhausted.

You rarely write the iterator protocol by hand, a generator is the easiest way to make one. Lists, strings, and files are iterable: ask them for an iterator with iter() and step through it with next().

Example
nums = [10, 20]
it = iter(nums)
print(next(it))
print(next(it))

# A generator is already an iterator:
squares = (n * n for n in [1, 2, 3])
print(next(squares))
Output
10
20
1

Where this shows up in real Python

Iterators are anything you can loop over: files, ranges, dict views, and your own classes that define how to step through their contents.

Commonly used Iterator tools

  • iter(obj), get an iterator from an iterable
  • next(it), pull the next value (StopIteration at the end)
  • for x in it, the loop that uses them automatically
  • import itertools, chain, islice, count, and friends

Official documentation: Python Glossary: Iterator

Related terms

Docstring A string on the first line of a function, class, or module that documents it, and stays available while the program runs.

A docstring is a string literal placed as the very first statement of a function, class, or module. By convention it uses triple quotes, so it can run to several lines without escaping anything.

The thing that makes it different from a comment is that Python keeps it. A docstring is stored on the object as __doc__, which is how help(), your editor's tooltips, and documentation generators can all show it. A # comment is discarded when the code is compiled and exists only in the source file.

One line is enough for most functions: say what it returns, not how it works. Save the longer form for when the arguments genuinely need explaining.

Example
def slugify(title):
    """Turn a title into a URL slug.

    Trims surrounding space, lowercases, and replaces spaces with hyphens.
    """
    return title.strip().lower().replace(" ", "-")


def shout(text):
    # Uppercase it. This comment does not survive into the program.
    return text.upper()


print(slugify("  Hello World  "))
print(slugify.__doc__.splitlines()[0])
print("shout has a docstring:", shout.__doc__ is not None)
Output
hello-world
Turn a title into a URL slug.
shout has a docstring: False

Official documentation: PEP 257: Docstring Conventions

Object-oriented Python

Modeling things as objects with their own data and behavior.

Class A blueprint for creating objects that bundle data (attributes) with behavior (methods).

A class defines a new type. You write it once with the class keyword, then create as many objects from it as you like. The special __init__ method runs automatically when each object is created and sets up its starting attributes via self.

Functions defined inside a class are its methods, behavior that lives with the data. Grouping data and behavior together is the core idea of object-oriented programming.

Example
class Dog:
    def __init__(self, name):
        self.name = name          # an attribute

    def speak(self):              # a method
        return f"{self.name} says woof"

rex = Dog("Rex")                  # create an object
print(rex.speak())
Output
Rex says woof

Where this shows up in real Python

Classes model real things, a User, an Account, a Report, bundling related data and the behavior that goes with it.

Commonly used Class tools

  • def __init__(self, ...), set up each new instance
  • self, the current instance inside a method
  • Account(100), call the class to create an object
  • isinstance(obj, Account), check an object’s type

Official documentation: Python Tutorial: Classes

Object A single value built from a class, with its own attributes and methods. Also called an instance.

An object (or instance) is created by calling a class like a function: Dog("Rex"). Each object carries its own attributes, so two objects of the same class can hold different values.

In Python everything is an object, numbers, strings, lists, even functions and classes themselves. You work with an object through its attributes and methods, reached with a dot.

Example
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

a = Point(1, 2)            # one object
b = Point(5, 9)            # another, with its own values
print(a.x, b.x)
print(isinstance(a, Point))
Output
1 5
True

Where this shows up in real Python

Everything in Python is an object, numbers, strings, functions, and every instance of your own classes.

Commonly used Object tools

  • type(obj), what class made this object
  • isinstance(obj, Cls), is it that type (or a subclass)
  • dir(obj), list its attributes and methods
  • vars(obj), its instance data as a dict

Related terms

Method A function that belongs to an object and is called on it with a dot, like text.upper().

A method is a function defined inside a class. Its first parameter is self, the particular object it was called on, so it can read and change that object's attributes.

You call a method with a dot and parentheses: account.deposit(50). Built-in types have methods too, "hi".upper() and [3, 1, 2].sort() are method calls.

Example
class Counter:
    def __init__(self):
        self.total = 0

    def add(self, n):          # a method; self is this object
        self.total += n

c = Counter()
c.add(5)
c.add(3)
print(c.total)
Output
8

Where this shows up in real Python

Methods are the behavior attached to objects, text.upper(), items.append(x), and the actions on your own classes.

Commonly used Method tools

  • obj.method(), call a method with the dot
  • self, the instance the method belongs to
  • @property, expose a method as if it were an attribute
  • @staticmethod / @classmethod, methods not tied to one instance

Official documentation: Python Tutorial: Method Objects

Attribute A piece of data stored on an object (or class), reached with a dot, like dog.name.

An attribute is a value attached to an object or a class. You usually create per-object attributes in __init__ by assigning to self, then read them with a dot: dog.name. Together, an object's attributes make up its state.

Attributes set on self belong to one object; attributes set in the class body are shared by every object of that class.

Example
class Book:
    pages = 0                  # class attribute (shared)

    def __init__(self, title):
        self.title = title     # instance attribute (per object)

b = Book("Python 101")
b.pages = 350                  # set an attribute
print(b.title, b.pages)
Output
Python 101 350

Where this shows up in real Python

Attributes hold an object’s state, a user’s name, an account’s balance, and they can differ from one instance to the next.

Commonly used Attribute tools

  • obj.attr, read or set an attribute directly
  • getattr(obj, 'x', default), read by name, with a fallback
  • setattr(obj, 'x', value), set by name
  • hasattr(obj, 'x'), check whether it exists

Related terms

Inheritance Defining a class that builds on another, reusing its attributes and methods.

Inheritance lets a child class reuse the attributes and methods of a parent class: class Dog(Animal):. The child can add new behavior or override a method to replace the parent's version.

Call super() to reuse the parent's version from inside the child. Reach for inheritance only when the child genuinely is a kind of the parent.

Example
class Animal:
    def speak(self):
        return "..."

class Dog(Animal):             # Dog inherits from Animal
    def speak(self):           # override the parent method
        return "Woof"

print(Dog().speak())
print(isinstance(Dog(), Animal))
Output
Woof
True

Where this shows up in real Python

Inheritance shares behavior across related classes, the pattern behind framework base classes for forms, models, and views you customize.

Commonly used Inheritance tools

  • class Car(Vehicle), Car inherits everything from Vehicle
  • super().__init__(...), run the parent’s setup too
  • def describe(self), override a method to specialize it

Official documentation: Python Tutorial: Inheritance

Related terms

Dunder method A special method with double underscores, like __init__ or __str__, that Python calls automatically.

A dunder method (“double-underscore”, also called a special or magic method) has a name like __init__, __str__, or __len__. You rarely call them directly; Python calls them for you when you create an object, print it, or use len().

Defining them lets your own objects behave like built-in types, print(obj) uses __str__ and len(obj) uses __len__.

Example
class Playlist:
    def __init__(self, songs):
        self.songs = songs

    def __len__(self):         # called by len()
        return len(self.songs)

    def __str__(self):         # called by print()
        return f"Playlist of {len(self)} songs"

p = Playlist(["a", "b", "c"])
print(len(p))
print(p)
Output
3
Playlist of 3 songs

Where this shows up in real Python

Dunder methods make your objects work with built-in syntax, print(), len(), for, and == all call them behind the scenes.

Commonly used Dunder method tools

  • __init__, set up a new instance
  • __str__ / __repr__, how the object prints
  • __len__, make len(obj) work
  • __eq__, define what == means
  • __iter__ / __next__, make the object loopable

Related terms

Descriptor An object that controls what happens when an attribute is read or assigned. You have used one every time you wrote @property.

A descriptor is any object defining __get__ or __set__ that is then used as a class attribute. Python routes attribute access through it, so the descriptor decides what reading or assigning that attribute actually does.

You have been using them all along without the name: property, classmethod, staticmethod, and even ordinary methods are descriptors underneath. Writing your own is worth it when the same managed-attribute logic is needed on several attributes, because one descriptor class covers all of them where a property would have to be copied per attribute.

__set_name__ is the piece that makes them practical: Python calls it as the class is created and tells the descriptor which name it was assigned to, so a single class works for name, email, and city alike.

Example
class NonEmpty:
    """A managed attribute that refuses blank text."""

    def __set_name__(self, owner, name):
        self.storage = "_" + name

    def __get__(self, instance, owner=None):
        return getattr(instance, self.storage)

    def __set__(self, instance, value):
        if not value.strip():
            raise ValueError(f"{self.storage[1:]} cannot be blank")
        setattr(instance, self.storage, value.strip())

class Contact:
    name = NonEmpty()

    def __init__(self, name):
        self.name = name

print(Contact("  Ada  ").name)

try:
    Contact("   ")
except ValueError as error:
    print("refused:", error)
Output
Ada
refused: name cannot be blank

Official documentation: Python HOWTO: descriptors

Protocol A type hint that describes what an object can do rather than what it inherits from, so duck-typed code can finally be annotated.

Python has always cared what an object can do rather than what it is: if it has a close() method you can close it, whatever its class. That is duck typing, and typing.Protocol is how you describe it to a type checker.

A protocol lists the methods something must have. Any class with those methods satisfies it — no inheritance, no import, no registration. That is the difference from a base class: a protocol describes a shape, and matching is structural rather than declared.

Adding @runtime_checkable lets you use isinstance() against it, with one honest limit: at runtime only the presence of each method is checked, not its signature or return type. The full check belongs to a checker such as mypy, before the program runs.

Example
from typing import Protocol, runtime_checkable

@runtime_checkable
class Sized(Protocol):
    """Anything you can call len() on."""
    def __len__(self) -> int: ...

class Basket:
    def __init__(self, items):
        self.items = items
    def __len__(self):
        return len(self.items)

class Switch:
    on = False

for thing in (Basket(["apple", "pear"]), [1, 2, 3], Switch()):
    name = type(thing).__name__
    print(f"{name:7s} sized: {isinstance(thing, Sized)}")
Output
Basket  sized: True
list    sized: True
Switch  sized: False

Official documentation: Python docs: typing.Protocol

Metaclass The class that creates a class. Normally `type`; writing your own runs code the moment a class is defined.

In Python a class is itself an object, so something has to create it. That something is its metaclass, and by default it is type. Writing your own means running code at the moment a class is defined rather than when one is used: validating that subclasses declare a required attribute, say, or recording each subclass in a registry.

They are worth recognizing and almost never worth writing. Nearly every historical use is now better served by __init_subclass__, a normal method on a normal class that Python calls whenever a subclass is defined, or by a class decorator.

The reason to prefer those is not style: code whose behavior was decided during class creation is genuinely hard to debug, and the person doing that debugging is usually you, later.

Example
class Registry(type):
    """Runs when a class is defined, not when one is used."""
    known = {}

    def __init__(cls, name, bases, namespace):
        super().__init__(name, bases, namespace)
        if bases:                      # skip the base class itself
            Registry.known[name.lower()] = cls

class Shape(metaclass=Registry):
    pass

class Square(Shape):
    pass

class Circle(Shape):
    pass

print("defined:", sorted(Registry.known))
print("type of Square:", type(Square).__name__)
Output
defined: ['circle', 'square']
type of Square: Registry

Official documentation: Python docs: metaclasses

Files, data & scripting

Reading the world outside your program and running real scripts.

pathlib Python's standard-library module for working with file and folder paths as objects.

pathlib represents a filesystem path as a Path object instead of a plain string. You build paths with the / operator (Path("data") / "file.txt") and read their parts with .name, .stem, and .suffix.

Because it behaves the same on Windows, macOS, and Linux, pathlib is the modern, cross-platform way to find, inspect, and rename files.

Example
from pathlib import Path

p = Path("photos") / "vacation.JPG"
print(p.name)
print(p.suffix.lower())
Output
vacation.JPG
.jpg

Where this shows up in real Python

pathlib is the backbone of file automation: finding, reading, writing, and renaming files without fragile string paths.

Commonly used pathlib tools

The Path object carries useful methods and properties:

  • Path('data') / 'in.csv', join paths with /
  • .exists(), does it exist
  • .glob('*.txt'), find matching files
  • .read_text() / .write_text(), read or write a whole file
  • .suffix / .name / .parent, extension, filename, folder
  • .mkdir(parents=True, exist_ok=True), create folders safely

Official documentation: Python Library Reference: pathlib

Related terms

File path The location of a file or folder, as text or a pathlib Path object.

A file path tells the program where a file lives. Paths can be absolute (from the drive root) or relative (from the current folder). Because separators and rules differ across operating systems, the modern approach is pathlib’s Path, which builds and inspects paths safely with the / operator.

Example
from pathlib import Path

p = Path("data") / "reports" / "june.csv"
print(p.name)        # june.csv
print(p.suffix)      # .csv
print(p.parent)      # data/reports
Output
june.csv
.csv
data/reports

Where this shows up in real Python

File paths appear whenever you read or write files: opening a config, saving a report, or walking a folder of images.

Path tools

  • Path('a') / 'b.txt', join path parts safely
  • .name / .suffix / .parent, filename, extension, folder
  • .exists(), check if it is there
  • .absolute(), get the full path
  • Path.home(), Path.cwd(), home and current folders

Official documentation: Python Library Reference: pathlib

Related lessons

Related terms

Module A file of Python code you can import and reuse in other programs.

A module is simply a .py file whose functions, classes, and variables you can reuse elsewhere with import. Python ships with a large standard library of ready-made modules, for math, dates, random numbers, files, and much more. After importing, reach inside with a dot (math.sqrt) or pull specific names out with from math import sqrt.

Example
import math               # a standard-library module
print(math.sqrt(16))      # use one of its functions
print(math.pi)            # and one of its constants

from datetime import date
print(date(2025, 1, 1).year)
Output
4.0
3.141592653589793
2025

Where this shows up in real Python

Modules let you reuse code across files and tap the standard library, os, json, datetime, and many more.

Commonly used Module tools

  • import json, bring in a whole module
  • from pathlib import Path, import just one name
  • import numpy as np, import under a shorter alias
  • dir(module), list what a module offers
  • help(thing), read its built-in documentation

Official documentation: Python Tutorial: Modules

Related lessons

Related terms

Import The statement that loads a module or name so you can use it in your file.

import brings code from another module into your file. import math loads the whole module (use it as math.sqrt); from math import sqrt pulls one name in directly; import numpy as np gives it a shorter alias. Imports usually go at the top of a file.

Example
import math
from datetime import date
import statistics as stats

print(math.sqrt(9))
print(date.today().year > 2000)
print(stats.mean([2, 4, 6]))
Output
3.0
True
4

Where this shows up in real Python

Every script beyond a few lines starts with imports, the standard library and the installed packages you build on.

Commonly used Import tools

  • import module, load a whole module
  • from module import name, import one name
  • import module as alias, import under a shorter name
  • from module import *, import everything (usually avoid)

Related lessons

Related terms

Command-line argument A value passed to a program on the command line when you run it, read in Python from sys.argv.

When you start a program with something like python greet.py Ada, everything after the script name is a command-line argument. Python collects them in sys.argv, a list of strings: sys.argv[0] is the script's own name, and your arguments start at index 1. They let one script work on any file or value instead of a hard-coded one.

Example
import sys

# Run as:  python greet.py Ada
print(sys.argv)        # ["greet.py", "Ada"]
print(sys.argv[1])     # "Ada"  — arguments start at index 1
Output
$ python greet.py Ada
['greet.py', 'Ada']
Ada

Where this shows up in real Python

Command-line arguments let one script behave differently each run, pass it a filename or an option instead of editing the code.

Commonly used Command-line argument tools

  • import sys, sys.argv holds the raw arguments
  • sys.argv[1:], everything after the script name
  • argparse, the standard way to define real flags and help

Official documentation: Python Library Reference: sys.argv

Related lessons

Related terms

argparse Python's standard-library module for building command-line interfaces that parse arguments and flags.

argparse turns command-line input into usable values. You create an ArgumentParser, declare the arguments you expect with add_argument, and call parse_args() to read them. It handles positional arguments, optional --flags, type conversion, and an automatic -h help screen.

It saves you from hand-parsing sys.argv and gives users clear errors when they get the command-line arguments wrong.

Example
import argparse

parser = argparse.ArgumentParser()
parser.add_argument("name")
parser.add_argument("--shout", action="store_true")

args = parser.parse_args(["Ada", "--shout"])
print(args.name, args.shout)
Output
Ada True

Where this shows up in real Python

argparse powers real command-line tools: named flags, defaults, types, and an auto-generated --help.

Commonly used argparse tools

  • ArgumentParser(), create the parser
  • .add_argument('path'), declare a positional argument
  • type=int, default=..., convert and supply a fallback
  • action='store_true', an on/off flag
  • .parse_args(), read the arguments into an object

Official documentation: Python Library Reference: argparse

JSON A text format for structured data, easily converted to and from Python objects.

JSON (JavaScript Object Notation) is the common language of web APIs and config files, and it looks almost exactly like Python dictionaries and lists. Python’s built-in json module converts between JSON text and Python objects: json.loads reads text into objects, json.dumps writes objects back to text.

Example
import json

text = '{"name": "Ada", "langs": ["Python", "C"]}'
data = json.loads(text)           # JSON text -> Python dict
print(data["name"])
print(json.dumps(data["langs"]))  # Python -> JSON text
Output
Ada
["Python", "C"]

Where this shows up in real Python

JSON is how you read API responses, save structured settings, and move data between programs.

Commonly used JSON tools

  • json.loads(text), parse JSON text into Python
  • json.dumps(obj), turn Python into JSON text
  • json.load(file), read JSON from a file
  • json.dump(obj, file), write JSON to a file
  • indent=2, pretty-print with indentation

Official documentation: Python Library Reference: json

Related terms

CSV A plain-text format for tabular data: one row per line, values separated by commas.

CSV (comma-separated values) is the lingua franca of spreadsheets and data exports. Each line is a row; commas separate the columns. Python’s built-in csv module reads and writes it safely, handling quoting and commas inside fields, and csv.DictReader gives each row as a dictionary keyed by column name.

Example
import csv, io

text = "name,age\nAda,36\nBo,29\n"
rows = list(csv.DictReader(io.StringIO(text)))
print(rows[0]["name"], rows[0]["age"])
print(len(rows), "rows")
Output
Ada 36
2 rows

Where this shows up in real Python

CSV is where data work often starts: exports from spreadsheets, reports, and simple datasets you clean or summarize.

Commonly used CSV tools

  • csv.reader(f), read rows as lists
  • csv.DictReader(f), read rows as dicts by header
  • csv.writer(f), write rows
  • csv.DictWriter(f, fieldnames), write dicts as rows
  • newline='', open CSV files with this to avoid blank lines

Official documentation: Python Library Reference: csv

Related terms

Context manager An object used with the with statement that sets up and cleans up a resource automatically.

A context manager guarantees setup and cleanup around a block of code, using the with statement. The classic example is with open(...) as f:, the file is closed automatically when the block ends, even if an error is raised. You can write your own with contextlib.contextmanager or by defining __enter__ and __exit__.

Example
with open("notes.txt", "w") as f:   # opened here
    f.write("hello")
# file is closed automatically here, even on error
print("saved")
Output
saved

Where this shows up in real Python

You will use with for files, database connections, locks, and temporary changes that must be undone afterwards.

Context manager tools

  • with open(path) as f, auto-close a file
  • with a, b:, manage several resources at once
  • contextlib.contextmanager, write one from a generator
  • __enter__ / __exit__, the methods that define one

Tools & environment

Managing packages, versions, and confidence in your code.

Virtual environment An isolated Python environment with its own installed packages, separate from the system Python.

A virtual environment is a private folder holding its own Python and its own installed packages. Creating one per project keeps each project's dependencies separate, so upgrading a package for one project can't break another.

Create one with python -m venv .venv, then activate it before installing anything with pip. Your shell prompt usually changes to show it's active.

Example
python -m venv .venv              # create it
source .venv/bin/activate         # activate (Windows: .venv\Scripts\activate)
pip install flask                 # installs only inside this environment

Where this shows up in real Python

A virtual environment keeps each project’s packages separate, so upgrading one project can’t break another.

Commonly used Virtual environment tools

  • python -m venv .venv, create one in the project folder
  • source .venv/bin/activate, activate it (macOS/Linux)
  • deactivate, step back out
  • which python, confirm which Python is active

Official documentation: Python Library Reference: venv

Related lessons

pip Python's package installer, used to add third-party libraries from PyPI.

pip installs packages that aren't part of the standard library, downloading them from the Python Package Index (PyPI). The basic command is pip install <package>, and you can pin a version with pip install flask==3.0.0.

Always install into an active virtual environment so packages stay project-local, and record them in requirements.txt for reproducibility.

Example
pip install requests             # install a package from PyPI
pip install flask==3.0.0         # pin an exact version
pip freeze > requirements.txt    # record what is installed

Where this shows up in real Python

pip adds third-party libraries, requests, flask, pandas, that aren’t in the standard library.

Commonly used pip tools

  • pip install requests, install a package
  • pip install requests==2.31.0, install a specific version
  • pip uninstall requests, remove it
  • pip list / pip freeze, see what’s installed
  • pip install -r requirements.txt, install from a list
Package A folder of related modules you can import; also a library you install with pip.

A package groups related modules under one name so they can be imported together (from urllib import request). In everyday use, “package” also means a third-party library you install from PyPI with pip, requests, flask, pandas, then import like any other module.

Example
# install once at the terminal:  pip install requests
import requests                  # a third-party package

from urllib import request        # a standard-library package

Where this shows up in real Python

Almost every real project pulls in packages, for HTTP, data, or web apps, installed with pip and listed in requirements.txt.

Commonly used Package tools

  • pip install name, install a package from PyPI
  • import name, use an installed package
  • from pkg import module, import a package’s module
  • pip show name, see a package’s details

Official documentation: Python Tutorial: Packages

Related terms

Dependency A package your project needs in order to run, including the packages that package needs.

A dependency is any package your code imports and therefore cannot run without. They are listed in your requirements file or your pyproject.toml, and pip installs them.

The part that catches people is that dependencies have dependencies. Asking for one package regularly installs five or twenty, and every one of those is code you now ship, pin, and inherit the security advisories of. pip show <package> lists the direct ones; the full tree is usually deeper than expected.

Which is why a dependency is a commitment rather than a free feature: it has to keep working when Python releases a new version, someone has to bump it, and a future reader has to learn it. That is not an argument against dependencies (writing your own HTTP client would be far worse), but it is an argument for checking one before adding it.

Example
DEPENDS_ON = {
    "reportmaker": ["jinja2", "click"],
    "jinja2": ["markupsafe"],
    "click": [], "markupsafe": [],
}

def installed_with(package, graph):
    """Every package that arrives alongside this one, sorted."""
    found = set()
    queue = list(graph.get(package, []))
    while queue:
        name = queue.pop()
        if name not in found:
            found.add(name)
            queue.extend(graph.get(name, []))
    return sorted(found)

print("asked for :", "reportmaker")
print("also gets :", installed_with("reportmaker", DEPENDS_ON))
print("direct    :", len(DEPENDS_ON["reportmaker"]), "-> total",
      len(installed_with("reportmaker", DEPENDS_ON)))
Output
asked for : reportmaker
also gets : ['click', 'jinja2', 'markupsafe']
direct    : 2 -> total 3
requirements.txt A text file listing a project's package dependencies, usually pinned to exact versions.

requirements.txt records the packages your project needs, one per line, normally pinned to an exact version like flask==3.0.0. Anyone can recreate your environment with pip install -r requirements.txt and get the same versions you used.

Generate it from your active virtual environment with pip freeze > requirements.txt. Pinning versions is what makes a project reproducible.

Example
flask==3.0.0
requests==2.31.0
python-dotenv==1.0.1

Where this shows up in real Python

requirements.txt lets anyone (including future you) recreate a project’s exact set of packages on a new machine.

Commonly used requirements.txt tools

  • pip freeze > requirements.txt, record current packages
  • pip install -r requirements.txt, install them all
  • requests==2.31.0, pin a version for reproducibility

Related terms

Version control A system that records snapshots of your project over time so you can review and undo changes. Git is the most common.

Version control tracks the history of your files as a series of commits, saved snapshots you can return to. It lets you experiment safely, see what changed and when, and undo mistakes. Git is by far the most widely used system.

The everyday loop is small and repeatable: stage your changes, commit them with a message, and repeat. Small, frequent commits make the history easy to read and to roll back.

Example
git init                          # start tracking a project
git add app.py                    # stage a change
git commit -m "Add dry-run mode"  # save a snapshot
git log --oneline                 # review the history

Where this shows up in real Python

Version control is your safety net: checkpoints before risky edits, a full history to undo mistakes, and a way to collaborate without overwriting work.

Commonly used Version control tools

  • git init, start tracking a folder
  • git add / git commit, stage and save a checkpoint
  • git status / git log, see changes and history
  • git diff, see exactly what changed

Official documentation: Git Documentation

Unit test A small automated check that verifies one piece of code behaves as expected.

A unit test runs a small part of your program with known inputs and checks the result. The simplest form is an assert statement: it raises an error if a condition isn't true and stays silent when it is. Testing pure helper functions lets you verify logic without touching real files or servers.

Python ships with the unittest module, and the popular third-party tool pytest lets you write tests as plain functions and runs them for you.

Example
def clean(name):
    return name.strip().lower()

# Quick checks before trusting it:
assert clean("  Ada ") == "ada"
assert clean("BO") == "bo"
print("All tests passed")
Output
All tests passed

Where this shows up in real Python

Unit tests prove a function works and catch regressions when you change code later, essential before trusting a script that touches real files.

Commonly used Unit test tools

  • assert result == expected, the simplest check
  • pytest, find and run test files
  • def test_thing():, pytest collects functions named test_*
  • pytest.raises(ValueError), assert that an error is raised

Official documentation: Python Library Reference: unittest

Profiling Measuring where a program actually spends its time, instead of guessing which line is slow.

Profiling means running your code under a tool that records how long each function took and how often it was called. The rule it exists to enforce is short: never optimize before measuring, because intuition about which line is slow is wrong often enough to be worthless.

Python ships two tools. cProfile answers “which part of my program is slow?”: run python -m cProfile -s cumtime script.py and read the tottime column, which is time spent in a function excluding what it called. timeit answers the narrower “which of these two lines is faster?” by running a snippet many times and reporting the best result.

Most real slowness, though, is not about how fast each operation is. It is about how many operations you do — and that you can often count without any tool at all.

Example
NAMES = [f"user{n:04d}" for n in range(5000)]
KNOWN = set(NAMES)

def scan_cost(names, wanted):
    """How many items a list scan has to look at to answer."""
    for position, name in enumerate(names, start=1):
        if name == wanted:
            return position
    return len(names)

print("list scan, last item :", scan_cost(NAMES, "user4999"), "comparisons")
print("list scan, no match  :", scan_cost(NAMES, "nobody"), "comparisons")
print("set lookup, either   : 1 comparison")
print("in the set?          ", "user4999" in KNOWN, "nobody" in KNOWN)
Output
list scan, last item : 5000 comparisons
list scan, no match  : 5000 comparisons
set lookup, either   : 1 comparison
in the set?           True False

Official documentation: Python docs: the Python profilers

Shipping your code

Packaging a project so other people can install, run, and trust it.

pyproject.toml The config file that describes a Python project as an installable package: its name, version, dependencies, and commands.

pyproject.toml is the single file that turns a folder of Python into something pip can install. It replaces the older setup.py, setup.cfg and MANIFEST.in that you will still meet in existing projects.

It has three jobs. [build-system] names the tool that builds the package. [project] describes it: name, version, description, supported Python versions, and dependencies. [project.scripts] declares the terminal commands it installs.

It is worth being clear about how this differs from requirements.txt, because the two are constantly confused. requirements.txt reproduces an environment for someone developing the project. pyproject.toml ships an artifact for someone who just wants to use it.

Example
import tomllib

PYPROJECT = """
[project]
name = "tidyup"
version = "0.1.0"
requires-python = ">=3.10"
dependencies = ["rich>=13.0"]

[project.scripts]
tidyup = "tidyup.cli:main"
"""

project = tomllib.loads(PYPROJECT)["project"]

print("name:        ", project["name"])
print("version:     ", project["version"])
print("needs python:", project["requires-python"])
print("dependencies:", project["dependencies"])
print("command:     ", "tidyup ->", project["scripts"]["tidyup"])
Output
name:         tidyup
version:      0.1.0
needs python: >=3.10
dependencies: ['rich>=13.0']
command:      tidyup -> tidyup.cli:main
Entry point A line in pyproject.toml that installs a terminal command pointing at one function in your package.

An entry point is what turns a script into a tool. Writing tidyup = "tidyup.cli:main" under [project.scripts] says: create a command called tidyup that imports the module tidyup.cli and calls the function main inside it. The colon separates the module from the function.

On install, pip writes a small launcher into the environment's bin/ directory. That directory is already on your PATH, which is why the command then works from any folder — the entire difference between typing python /some/path/tool.py and typing tidyup.

The mechanism is far less magical than it looks. As the example shows, resolving module:function is just an import followed by getattr.

Example
import importlib

# Exactly the string form used in [project.scripts].
TARGET = "json:dumps"

module_name, function_name = TARGET.split(":")
module = importlib.import_module(module_name)
command = getattr(module, function_name)

print("module:  ", module_name)
print("function:", function_name)
print("calling: ", command({"packaged": True}))
Output
module:   json
function: dumps
calling:  {"packaged": true}
Continuous integration (CI) Automatically running your tests and checks on a clean machine every time you push code.

Continuous integration means a server checks out your project on a fresh machine and runs your checks, on every push, without being asked. On GitHub this is GitHub Actions, configured by a YAML file in .github/workflows/.

The value is in the word clean. That machine has none of your locally installed packages, none of your uncommitted files, and no residue from an earlier test, so it catches a category of bug that is invisible on your own computer by definition. A dependency you installed months ago and forgot to declare will fail there and nowhere else.

A workflow contains jobs, and each job is a list of steps run in order. Any step that exits non-zero fails the job and stops the rest, which is why the whole thing needs no wiring beyond listing the commands.

Example
# What a pipeline does, in miniature: run steps until one fails.
STEPS = [("checkout", True), ("install", True), ("ruff check", False), ("pytest", True)]

def run_pipeline(steps):
    """Return the name of the first failing step, or None."""
    for name, passed in steps:
        print(f"  {name}: {'ok' if passed else 'FAILED'}")
        if not passed:
            return name
    return None

failed = run_pipeline(STEPS)
print("failed at:", failed)
print("exit code:", 0 if failed is None else 1)
Output
  checkout: ok
  install: ok
  ruff check: FAILED
failed at: ruff check
exit code: 1
Container Your program packaged with its whole environment (Python, the OS, and system libraries) so it runs identically anywhere.

A container is a running instance of an image: a built, unchanging bundle containing a stripped-down operating system, a Python installation, your dependencies, and your code. Docker is the usual tool for building and running them. The image is to a container what a class is to an object: one definition, many instances.

The point is what it captures that smaller tools do not. A virtual environment pins your Python packages and nothing else; a container pins the Python version, the operating system, and the system libraries underneath as well.

Two things surprise everyone once. A container cannot see your files unless you deliberately mount a folder into it, and anything it writes outside such a folder disappears when it stops. Both are the isolation working as intended.

Example
# What each layer of tooling actually pins.
LAYERS = {
    "requirements.txt": ["python packages"],
    "pyproject.toml": ["python packages", "your code as a package"],
    "container": ["python packages", "your code as a package",
                  "the python version", "the OS and system libraries"],
}

for name, pinned in LAYERS.items():
    print(f"{name:18s} pins {len(pinned)}")
    for item in pinned:
        print(f"{'':20s}- {item}")
Output
requirements.txt   pins 1
                    - python packages
pyproject.toml     pins 2
                    - python packages
                    - your code as a package
container          pins 4
                    - python packages
                    - your code as a package
                    - the python version
                    - the OS and system libraries

Official documentation: Docker docs: Dockerfile reference

YAML A human-readable config format that uses indentation instead of brackets, used for CI workflows, Docker Compose, and app settings.

YAML is a configuration format designed to be read and edited by people. Structure comes from indentation rather than brackets or braces, which makes it look a lot like Python and reads far more comfortably than JSON for anything hand-written.

You will meet it as soon as you touch tooling: GitHub Actions workflows, Docker Compose files, Kubernetes manifests, and countless application config files are all YAML. In Python it is read with yaml.safe_load() from the third-party PyYAML package (pip install pyyaml), which turns a document into ordinary dictionaries and lists.

Always use safe_load rather than load. Plain load can construct arbitrary Python objects from a document, so a hostile file could run code.

Example
import yaml

CONFIG = """
name: tests
on:
  push:
    branches: [main]
versions: ["3.11", "3.12"]
debug: no
"""

data = yaml.safe_load(CONFIG)

print("name:    ", data["name"])
print("versions:", data["versions"])
print("keys:    ", list(data))
print("debug is:", repr(data["debug"]))
Output
name:     tests
versions: ['3.11', '3.12']
keys:     ['name', True, 'versions', 'debug']
debug is: False

Official documentation: PyYAML documentation

Web development

Serving pages and handling requests with a framework.

Web framework A library that handles the plumbing of web requests so you can focus on your app's logic. Flask and Django are examples.

A web framework takes care of the repetitive parts of serving a website, listening for HTTP requests, matching URLs to code, and building responses, so you write only the parts unique to your app. You map a URL to a function, and the framework runs it when a request arrives.

Flask is a small, beginner-friendly Python framework; Django is a larger, batteries-included one. Both let your Python code answer requests with web pages.

Example
# A minimal Flask app
from flask import Flask

app = Flask(__name__)

@app.route("/")          # map a URL to a function
def home():
    return "Hello, world!"

Where this shows up in real Python

Web frameworks (Flask, Django) handle the repetitive parts of serving a site, routing, templates, requests, so you write the interesting bits.

Commonly used Web framework tools

  • @app.route('/'), map a URL to a function
  • render_template('page.html'), fill an HTML template
  • request, read the incoming request
  • return, the response sent back to the browser

Official documentation: Flask Documentation

Related lessons

Related terms

HTTP The request/response protocol browsers and servers use to communicate on the web.

HTTP (HyperText Transfer Protocol) is the language of the web. A browser sends an HTTP request for a URL, and a server sends back an HTTP response, usually an HTML page, along with a status code like 200 (OK) or 404 (Not Found).

Requests use methods: GET fetches a page, while POST sends data to change something (like submitting a form). Your Python code runs on the server and produces the response.

Example
GET /about HTTP/1.1          <- the browser asks
Host: example.com

HTTP/1.1 200 OK              <- the server answers
Content-Type: text/html

<h1>About us</h1>

Where this shows up in real Python

HTTP is the language of every web request: browsers, APIs, and your own requests.get() calls all speak it.

Commonly used HTTP tools

  • GET / POST, fetch data / send data
  • 200, 404, 500, OK, not found, server error
  • headers, extra info like content type
  • requests.get(url), make an HTTP request from Python

Official documentation: MDN Web Docs: HTTP

Related lessons

API A defined way for programs to talk to each other and exchange data.

An API (application programming interface) is a contract that lets one program request data or actions from another. On the web, you call an API by sending an HTTP request to a URL and usually get JSON back. In Python the requests library is the usual tool, and a status_code tells you whether it worked.

Example
# with the requests package installed:
import requests

resp = requests.get("https://api.example.com/users/1")
if resp.status_code == 200:
    user = resp.json()        # parse JSON into a dict
    print(user["name"])

Where this shows up in real Python

APIs power live data in your scripts: weather, prices, maps, payments, and your own web services.

Commonly used API tools

  • requests.get(url), fetch data from an API
  • requests.post(url, json=...), send data
  • .status_code, 200 OK, 404 not found, and so on
  • .json(), parse the JSON response
  • params={…}, headers={…}, query parameters and headers

Official documentation: requests: HTTP for Humans

Related terms

Route A mapping from a URL path to the function that runs when someone visits it.

In a web framework, a route connects a URL path like /about to a function (a view function) that builds the response. In Flask you create one with the @app.route("/path") decorator placed above the function.

When a request arrives, the framework reads the path, finds the matching route, runs its function, and sends back whatever it returns.

Example
from flask import Flask
app = Flask(__name__)

@app.route("/about")     # this route maps /about to about()
def about():
    return "About this site"

Where this shows up in real Python

Routes connect URLs to view functions, the map that decides which code runs for /, /about, or /users/42.

Commonly used Route tools

  • @app.route('/path'), bind a URL to a function
  • methods=['GET', 'POST'], accept form submissions too
  • /user/<id>, capture part of the URL as a value

Official documentation: Flask Documentation: Routing

Related lessons

Template An HTML file with placeholders that a web framework fills in with data before sending it to the browser.

A template keeps your HTML in its own file with placeholders for the changing parts, so markup stays separate from Python logic. Flask uses the Jinja template engine: {{ name }} drops in a value and {% for item in items %} repeats a block.

render_template("index.html", name="Ada") loads the file from the templates/ folder and fills in the values you pass. Jinja auto-escapes them, so user text can't inject HTML.

Example
<h1>Hello, {{ name }}!</h1>
<ul>
  {% for note in notes %}
    <li>{{ note }}</li>
  {% endfor %}
</ul>

Where this shows up in real Python

Templates generate HTML pages with data filled in, the list of notes, the logged-in user’s name, without pasting HTML into your Python.

Commonly used Template tools

  • {{ value }}, drop a value into the page
  • {% for x in items %}, repeat markup for each item
  • {% if user %}, show markup conditionally
  • url_for('static', filename=...), build links safely
HTML form A part of a web page that collects input from the user and submits it to the server.

An HTML <form> gathers user input, text boxes, checkboxes, buttons, and sends it to a server when submitted. Each field has a name, which becomes the key your server code reads. A form that changes data uses method="post".

In Flask, submitted values arrive in request.form, which works like a dictionary keyed by each field's name. Always validate that input before trusting it.

Example
<form method="post" action="/add">
  <input name="note">
  <button type="submit">Add</button>
</form>

Where this shows up in real Python

Forms collect user input on the web, logins, search boxes, comment fields, and send it to a route that validates and stores it.

Commonly used HTML form tools

  • method='post', send data in the request body, not the URL
  • name='email', the key your server reads the value by
  • request.form['email'], read a submitted value in Flask
  • .strip() and validate, never trust input as-is

Official documentation: MDN Web Docs: Web Forms

Related lessons

Related terms

Static file A file like CSS, JavaScript, or an image that the server sends to the browser unchanged.

Static files don't change per visitor, so the server sends them exactly as they are, no code runs to build them. In Flask they live in a static/ folder, separate from the templates that Python fills in.

Link to them with url_for("static", filename="style.css") rather than hardcoding the path, so the URL stays correct even if the app moves.

Example
<link rel="stylesheet"
      href="{{ url_for('static', filename='style.css') }}">

Where this shows up in real Python

Static files are the CSS, JavaScript, and images a site serves unchanged, the parts that make pages look and behave the way they do.

Commonly used Static file tools

  • static/ folder, where CSS, JS, and images live
  • url_for('static', filename='main.css'), build a static URL
  • link / script / img, the tags that load them

Official documentation: Flask Documentation: Static Files

More terms

Concurrency Running several tasks in overlapping stretches of time, so waiting for one does not stop the others.

Concurrency means a program makes progress on several tasks during the same stretch of time instead of finishing each one before starting the next. It is what turns ten network requests that take two seconds in a row into ten requests that take a fifth of a second together.

The crucial limit is that it only reclaims waiting. If your program is genuinely busy calculating, there is no idle time to fill and concurrency buys you nothing. That case needs separate processes and more CPU cores. Ask which one you have before choosing a tool: is the program working, or is it waiting?

Python offers two everyday routes. A thread pool hands the jobs to a small team of workers, and is usually the simplest change to make. async/await keeps one worker that switches tasks whenever it would otherwise sit idle.

Example
import time
from concurrent.futures import ThreadPoolExecutor

def fetch_page(number):
    time.sleep(0.2)          # stands in for a network call
    return f"page {number}"

start = time.perf_counter()
[fetch_page(n) for n in range(10)]
print(f"one at a time: {time.perf_counter() - start:.1f}s")

start = time.perf_counter()
with ThreadPoolExecutor(max_workers=5) as pool:
    list(pool.map(fetch_page, range(10)))
print(f"5 at a time:   {time.perf_counter() - start:.1f}s")
Output
one at a time: 2.0s
5 at a time:   0.4s

Official documentation: Python docs: concurrent.futures

Coroutine A function defined with async def, which can pause at each await and resume later.

A coroutine is a function that can pause partway through and be resumed. You write one with async def, and mark its pause points with await.

The part that catches everyone out: calling a coroutine function does not run it. You get a coroutine object back, the work packaged up and ready, waiting for something to drive it. That something is normally an event loop, started by asyncio.run(). Inside async code, await both drives a coroutine and hands you its result.

The point of pausing is that while one coroutine waits at an await, the loop runs another. One worker, never idle — which is how a single thread can keep thousands of connections busy.

Example
import asyncio

async def greet(name):
    await asyncio.sleep(0.1)      # a pause point, not a freeze
    return f"Hello, {name}!"

# Calling it just builds the coroutine:
coro = greet("Ada")
print(type(coro).__name__)
coro.close()                  # we never ran this one; tidy it away

# Running it needs an event loop:
print(asyncio.run(greet("Ada")))

# gather runs several at once, results in the order given:
async def main():
    return await asyncio.gather(greet("Ada"), greet("Sam"))

print(asyncio.run(main()))
Output
coroutine
Hello, Ada!
['Hello, Ada!', 'Hello, Sam!']

Official documentation: Python docs: Coroutines and Tasks

Dataclass A class decorated with @dataclass, which generates its __init__, __repr__, and __eq__ from the fields you list.

A dataclass is a normal class with the boilerplate written for you. Decorate it with @dataclass, list the fields with type hints, and Python generates the constructor, a readable __repr__, and field-by-field equality.

It sits between a dictionary and a hand-written class. A dict is flexible but a mistyped key is a silent bug; a hand-written class means writing the methods yourself. A dataclass gives you named fields, a useful printout, and a real type a checker understands, for about three lines.

Example
from dataclasses import dataclass, field

@dataclass
class Book:
    title: str
    year: int
    tags: list[str] = field(default_factory=list)

book = Book("Dune", 1965)
print(book)
print(Book("Dune", 1965) == book)
Output
Book(title='Dune', year=1965, tags=[])
True

Official documentation: Python docs: dataclasses

DataFrame The pandas table: named columns, labeled rows, and different types in different columns.

A DataFrame is the pandas object holding a whole table, a spreadsheet Python can ask questions about. Columns have names, rows have index labels, and unlike a NumPy array each column can hold a different type, so names, dates and numbers live together comfortably.

Two operations do most of the work. Selecting picks columns: single brackets for one (giving a Series), double brackets for several. Filtering picks rows by writing a condition, which produces True/False per row and keeps the True ones.

The habit worth building is to look before calculating: df.head() to see it, df.shape to size it, df.isna().sum() to count what is missing.

Example
import pandas as pd

grades = pd.DataFrame({
    "student": ["Ada", "Sam", "Rae"],
    "subject": ["math", "math", "art"],
    "score":   [91, 78, 84],
})

print(grades)
print()
print("shape:", grades.shape)
print()
print(grades[grades["score"] > 80])
Output
  student subject  score
0     Ada   math     91
1     Sam   math     78
2     Rae     art     84

shape: (3, 3)

  student subject  score
0     Ada   math     91
2     Rae     art     84

Official documentation: pandas docs: 10 minutes to pandas

Event loop The loop at the heart of an interactive program: check for input, update state, show the result, repeat.

An event loop is what makes a program interactive rather than something that runs once and stops. It repeats forever: check whether anything happened, decide what it means, update the state, and display the result.

Games write the loop by hand: a while loop calling pygame.event.get(). GUI toolkits run it for you and call the functions you attached to each widget, which is what root.mainloop() does in Tkinter. Web frameworks and asyncio are the same idea again.

The important contrast is with input(), which blocks: the program stops dead until someone presses Enter. A game loop never blocks — it keeps turning whether or not you touch anything, which is why animation continues while you sit still.

Example
COMMANDS = ["look", "take lamp", "quit", "look"]
state = {"running": True, "bag": []}

for command in COMMANDS:          # a real loop would wait for input here
    if not state["running"]:
        break
    if command == "quit":
        state["running"] = False
        print("goodbye")
    elif command.startswith("take "):
        state["bag"].append(command[5:])
        print("you take the", command[5:])
    else:
        print("you are in a dim room")

print("bag:", state["bag"])
Output
you are in a dim room
you take the lamp
goodbye
bag: ['lamp']
Feature An input column a model learns from, one of the facts you already know about each example.

A feature is an input. In a table of houses, size and bedrooms are features; the price you want to predict is the label (or target). Every row pairs its features with its answer.

By convention the features live in a two-dimensional array called X (capital, because it is a table) with one row per example and one column per feature. The labels live in a single column called y. Every Python machine-learning example you read will use those two names.

Deciding which columns are features and which is the label is the first step of any project, and choosing good features usually matters more than choosing a clever algorithm.

Example
import numpy as np

# One row per house, one column per feature: size and bedrooms.
X = np.array([[50, 1],
              [75, 2],
              [110, 3]])

# One label per row: the price we want to predict.
y = np.array([150, 205, 295])

print("X shape:", X.shape, "-> 3 examples, 2 features each")
print("y shape:", y.shape, "-> one label per example")
print("first example:", X[0], "->", y[0])
Output
X shape: (3, 2) -> 3 examples, 2 features each
y shape: (3,) -> one label per example
first example: [50  1] -> 150
GIL (Global Interpreter Lock) The lock in CPython that lets only one thread run Python code at a time.

The Global Interpreter Lock is a lock inside CPython (the standard Python) that allows only one thread to run Python code at any single instant. Threads take turns holding it, swapping many times a second.

For waiting this costs nothing: a thread hands the GIL back the moment it blocks on the network or the disk, so other threads run during that gap. This is exactly why thread pools make network code dramatically faster.

For calculation it is decisive. Four threads crunching numbers take turns on one lock and finish in roughly the time one thread would, plus the cost of swapping. The answer there is ProcessPoolExecutor: separate processes each get their own interpreter, their own GIL, and their own CPU core.

Example
import time
from concurrent.futures import ThreadPoolExecutor

def count_primes(limit):
    return sum(all(n % d for d in range(2, int(n ** 0.5) + 1))
               for n in range(2, limit))

start = time.perf_counter()
[count_primes(500_000) for _ in range(4)]
print(f"one at a time: {time.perf_counter() - start:.1f}s")

start = time.perf_counter()
with ThreadPoolExecutor(max_workers=4) as pool:
    list(pool.map(count_primes, [500_000] * 4))
print(f"4 threads:     {time.perf_counter() - start:.1f}s")
Output
one at a time: 2.2s
4 threads:     2.2s

Official documentation: Python docs: Global interpreter lock

Logging Recording what a program does as it runs, with a level on each message so you can filter the detail later.

Logging is the grown-up replacement for scattered print() calls. Python's built-in logging module records messages with a level saying how much each one matters: DEBUG, INFO, WARNING, ERROR, and CRITICAL.

The point is that you choose a threshold when the program runs, not when you write it. Set it to INFO and the DEBUG messages disappear; set it to DEBUG when something is wrong and they all come back, without editing a single line. Logs can also carry timestamps and be written to a file, which is what lets you work out what a script did while nobody was watching.

Example
import logging, sys

logging.basicConfig(level=logging.INFO,
                    format="%(levelname)s %(message)s",
                    stream=sys.stdout)

logging.debug("cache warmed")      # below the threshold, not shown
logging.info("fetched 3 records")
logging.warning("1 record had no year")
Output
INFO fetched 3 records
WARNING 1 record had no year

Official documentation: Python docs: Logging HOWTO

Machine learning Working out a rule from examples instead of writing the rule by hand.

Machine learning is what you do when the rule is not knowable in advance. Rather than writing the logic yourself, you supply examples and let a program work out the pattern that connects them.

In supervised learning (nearly all of it in practice) each example arrives with the right answer attached. Predicting a number is regression; predicting a category is classification. In unsupervised learning there are no answers, and the job is to find structure in the data itself.

Two things are worth holding onto. A model always produces an answer, however nonsensical the input, so a prediction without an evaluation is worthless. And a model reproduces whatever its training data contained, including biases nobody intended.

Example
# The rule a human writes:
def rule_based(size_m2):
    return size_m2 * 2.7

# The rule the examples give you:
sizes = [50, 75, 110]
prices = [150, 205, 295]
learned_rate = sum(prices) / sum(sizes)

print("human guess per m2: ", 2.7)
print("learned from data:  ", round(learned_rate, 3))
print("they disagree by:   ", round(abs(2.7 - learned_rate) * 90, 1), "on a 90m2 house")
Output
human guess per m2:  2.7
learned from data:   2.766
they disagree by:    5.9 on a 90m2 house
Matplotlib The standard Python plotting library: bar, line, scatter and histogram charts from your data.

Matplotlib turns a table into a picture. It is imported as import matplotlib.pyplot as plt by near-universal convention, and you build a chart by calling functions in order: create a figure, draw something, label it, show it.

Choosing the chart type is the real skill, because each answers a different question. plt.bar() compares separate categories, plt.plot() shows change over a continuous run such as time, plt.scatter() shows how two numbers relate, and plt.hist() shows how one set of numbers is distributed — the one that reveals whether an average is hiding two different groups.

Always add a title and both axis labels. An unlabeled chart shows a shape without saying what it measures, which makes it decoration rather than evidence.

Example
import matplotlib.pyplot as plt

months = ["Jan", "Feb", "Mar", "Apr"]
sales = [120, 145, 133, 178]

plt.figure(figsize=(6, 3.5))
plt.bar(months, sales, color="#2b7cd3")
plt.title("Monthly sales")
plt.xlabel("month")
plt.ylabel("units sold")
plt.show()

print("best month:", months[sales.index(max(sales))])
Output
best month: Apr

Official documentation: Matplotlib docs: quick start guide

Related lessons

NumPy The library for fast math over whole arrays of numbers at once, without writing a loop.

NumPy gives Python the array: a list built for math. Its central idea is the vectorized operation: you write the calculation once and it is applied to every element, with the looping done in compiled code rather than in Python.

The catch is that an array holds one type of value, normally numbers. That restriction is where the speed comes from, and it is also why tables mixing names, dates and categories belong in a DataFrame instead, which is itself built on NumPy arrays.

Watch out for the difference from lists: on a list * repeats and + joins, while on an array both do arithmetic to every element.

Example
import numpy as np

prices = np.array([1.20, 2.50, 0.60, 1.80])

print("with tax:", (prices * 1.2).round(2))
print("mean:    ", prices.mean())
print("shape:   ", prices.shape)
print("over 1:  ", prices[prices > 1.0])

plain = [1.20, 2.50]
print("a list repeats:", plain * 2)
Output
with tax: [1.44 3.   0.72 2.16]
mean:     1.525
shape:    (4,)
over 1:   [1.2 2.5 1.8]
a list repeats: [1.2, 2.5, 1.2, 2.5]
ORM A library that maps database tables to classes, so you work with rows as Python objects instead of writing SQL.

An ORM (object-relational mapper) lets you describe each table as a Python class. The library creates the table from that definition and turns method calls into SQL, so you query with Python expressions rather than query strings. Django’s ORM and SQLAlchemy are the two most common in Python.

The trade-off is straightforward: less SQL to write, more framework to learn. ORMs pay off on applications with many related tables and a schema that changes over time. For a script with one or two tables, plain sqlite3 is usually simpler and easier to see through.

Example
# Django ORM: the class defines the table...
class Book(models.Model):
    title = models.CharField(max_length=200)
    year = models.IntegerField()

# ...and queries read as Python, generating SQL underneath.
recent = Book.objects.filter(year__gte=2000).order_by("title")

Official documentation: Django docs: making queries

Overfitting When a model learns its training examples too specifically and predicts new data badly.

Overfitting is a model memorizing instead of generalizing. It learns the exact training examples, including their noise and accidents, and then performs poorly on anything it has not seen.

The signature is a pair of scores: excellent on the training data, much worse on held-back data. That gap is the thing to watch, and it is invisible unless you kept a test set. A model with both scores mediocre has the opposite problem, underfitting: too simple to capture the real pattern.

The usual cures are more training data or a simpler, less flexible model. Counter-intuitively, flexibility is the danger: it is what gives a model enough freedom to bend towards every training point rather than describing the trend.

Example
import numpy as np

x_train = np.array([1, 2, 3, 4, 5, 6])
y_train = np.array([52, 55, 61, 64, 70, 72])
x_test, y_test = np.array([7, 8]), np.array([79, 85])

def error(model, x, y):
    return round(float(np.abs(np.polyval(model, x) - y).mean()), 2)

straight = np.polyfit(x_train, y_train, 1)
wiggly = np.polyfit(x_train, y_train, 4)

print("straight  train:", error(straight, x_train, y_train),
      "| test:", error(straight, x_test, y_test))
print("wiggly    train:", error(wiggly, x_train, y_train),
      "| test:", error(wiggly, x_test, y_test))
Output
straight  train: 0.78 | test: 2.75
wiggly    train: 0.53 | test: 14.95
Parameterized query A query where values are sent separately from the SQL text, using placeholders instead of string formatting.

A parameterized query puts a placeholder (? in SQLite) everywhere a value belongs, and passes the actual values as a separate argument. The database receives the instruction and the data as two distinct things.

This matters for two reasons. Values containing quotes or other special characters work without any escaping, and a value can never be interpreted as an instruction, which is the class of bug called SQL injection. Building queries with f-strings is the habit that causes both problems.

Example
import sqlite3

conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE members (name TEXT)")
conn.execute("INSERT INTO members VALUES (?)", ("O'Brien",))

# The apostrophe is data, not syntax. Nothing needs escaping.
row = conn.execute("SELECT name FROM members WHERE name = ?", ("O'Brien",)).fetchone()
print(row[0])
Output
O'Brien

Official documentation: Python docs: sqlite3 placeholders

Race condition A bug where the result depends on the unpredictable order in which concurrent workers interleave.

A race condition happens when two workers touch the same data at the same time and the answer depends on which one happens to get there first. Nothing crashes and no exception is raised — the result is simply wrong, and wrong differently each run.

The usual culprit looks harmless. counter += 1 is really three steps: read the value, add one, write it back. A worker can be interrupted between any two of them. If both workers read 5 before either writes, both write 6, and one increment disappears.

The textbook fix is a lock around the read-modify-write. The better everyday fix is to share nothing at all: give each worker its own data, have it return a result, and combine the results at the end. A thread or process pool already works this way if you use its return values.

Example
counter = 0

# Two workers each want to add 1. One possible interleaving:
a_read = counter        # worker A reads 0
b_read = counter        # worker B also reads 0
counter = a_read + 1    # A writes 1
counter = b_read + 1    # B writes 1, overwriting A

print("after two increments:", counter)
print("should have been:    ", 2)
Output
after two increments: 1
should have been:     2

Official documentation: Python docs: Lock objects

Regular expression A compact pattern describing text to find, extract, or replace.

A regular expression (regex) describes a shape of text rather than exact characters, so one pattern can match every date, error code, or email address in a document. Python's re module provides search for the first match, findall for all of them, and sub for replacement.

The building blocks are character classes (\d a digit, \w a word character, \s whitespace), quantifiers (+ one or more, * zero or more, {3} exactly three), anchors (^ and $), and parentheses to capture part of a match.

Example
import re

line = "2026-07-27 ERROR E404 missing file"

print(re.findall(r"E\d{3}", line))
match = re.match(r"^(\d{4}-\d{2}-\d{2}) (\w+)", line)
print(match.group(1), match.group(2))
Output
['E404']
2026-07-27 ERROR
Series A single labeled column of a pandas table, one column of a DataFrame on its own.

A Series is one column: a sequence of values plus an index labeling each one. Selecting a single column from a DataFrame with single brackets gives you a Series; selecting several with double brackets gives another DataFrame. Confusing the two is the most common early pandas mistake, and the giveaway is a method that unexpectedly does not exist.

A Series is essentially a NumPy array that remembers its labels, so it carries the same statistics (.mean(), .sum(), .max()) and the same filtering by condition. It is also what a groupby summary hands back, with the group names as its index.

Example
import pandas as pd

scores = pd.Series([91, 78, 84], index=["Ada", "Sam", "Rae"])

print(scores)
print()
print("type:", type(scores).__name__)
print("mean:", scores.mean())
print("Ada scored:", scores["Ada"])
print()
print(scores[scores > 80])
Output
Ada    91
Sam    78
Rae    84
dtype: int64

type: Series
mean: 84.33333333333333
Ada scored: 91

Ada    91
Rae    84
dtype: int64

Official documentation: pandas docs: Series

SQL The language used to create, query, and change data in a relational database.

SQL (Structured Query Language) is how you talk to a relational database. It is declarative: you describe the result you want and the database works out how to produce it, rather than you writing the loops yourself.

SQL is not a separate program to install. It is ordinary text your Python code sends over a connection, most often with cursor.execute(). The four statements that cover most work are SELECT to read, INSERT to add, UPDATE to change, and DELETE to remove.

Example
import sqlite3

conn = sqlite3.connect(":memory:")
conn.execute("CREATE TABLE books (title TEXT, year INTEGER)")
conn.execute("INSERT INTO books VALUES (?, ?)", ("Dune", 1965))

row = conn.execute("SELECT title FROM books WHERE year < ?", (1970,)).fetchone()
print(row[0])
Output
Dune

Official documentation: SQLite: SQL language reference

Training data The examples a model learns from, kept separate from the test data used to judge it.

Training data is the set of examples a model learns its pattern from. The crucial companion idea is that it must not be the data you judge the model on.

Before training, hold some examples back as a test set. 20% is a common choice. Train on the rest, then score on the held-back rows. Those rows stand in for the future data the model will actually meet, so their score is the only honest estimate of how it will perform.

Scoring on data the model already saw measures memory rather than prediction, and it is how overfitting goes unnoticed. If the rows are grouped by label, shuffle before splitting or the test set may contain only one class.

Example
examples = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j"]

split_at = int(len(examples) * 0.8)
train = examples[:split_at]
test = examples[split_at:]

print("train on:", train)
print("test on: ", test)
print("nothing appears in both:", set(train).isdisjoint(test))
Output
train on: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h']
test on:  ['i', 'j']
nothing appears in both: True
Type hint An annotation recording what type a variable, argument, or return value is meant to be.

A type hint records the type a piece of code expects. You write it after a parameter with a colon, and after the parameter list with an arrow for the return value. Common notations are list[str], dict[str, int], and str | None for a value that might be missing.

Python does not check hints when the program runs, which surprises everyone once. They are metadata, written for the people and tools reading your code. A separate checker such as mypy reads them and reports contradictions before the code ever executes, and editors use them for autocomplete and warnings.

Example
def label(count: int) -> str:
    return f"{count} items"

print(label(3))
print(label("three"))   # wrong type, but Python does not object
Output
3 items
three items