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.
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)
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 toisinstance(value, int), check whether it points to a given typedel name, remove a name so it no longer points to anything
Official documentation: Python Language Reference: Assignment statements
Related lessons
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.
# Correct syntax: a colon, then an indented block
if 3 > 2:
print("valid Python")
# Missing the colon would raise, before anything runs:
# SyntaxError: expected ':'
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
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.
greeting = "hello"
print(greeting.upper()) # methods return a NEW string
print(greeting[0]) # index: the first character
print(f"{greeting}, world!") # f-string formatting
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 endsf'{value}', f-strings drop values straight into text
Official documentation: Python Library Reference: Text Sequence Type, str
Related lessons
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.
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
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 inta // b, floor (whole-number) divisiona % b, the remainderabs(n), distance from zero2 ** 10, exact powers, no overflowdivmod(a, b), quotient and remainder together
Official documentation: Python Library Reference: Numeric Types, int, float, complex
Related lessons
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.
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
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 stringround(x, 2), round to a number of decimal placesabs(x), drop the signf'{x:.2f}', format to a fixed number of decimalsimport math, math.floor, math.ceil, math.sqrt, …
Official documentation: Python Tutorial: Floating-Point Arithmetic — Issues and Limitations
Related lessons
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.
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
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/Falseany(items) / all(items), is any/are all of them truthybool(x), see how a value is judged truthy or falsy
Official documentation: Python Library Reference: Truth Value Testing
Related lessons
Related terms
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.
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
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 copylen(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.
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
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 parenthesesx, y = point, unpack into variablespoint[0], index like a listlen(point), item in point, length and membershiptuple([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.
emails = ["[email protected]", "[email protected]", "[email protected]"]
unique = set(emails) # duplicates dropped
print(len(unique))
print("[email protected]" in unique) # fast membership test
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 missinga | b, union, items in eithera & b, intersection, items in botha - b, difference, in a but not bset(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.
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
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
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.
score = 72
if score >= 90:
grade = "A"
elif score >= 60:
grade = "pass"
else:
grade = "fail"
print(grade)
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 conditionsand / or / not, combine conditionsx 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.
temperature = 30
if temperature > 25:
print("Warm")
else:
print("Cool")
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 conditionallyelif other:, test another caseelse:, the fallback branchif x and y:, combine conditions
Official documentation: Python Tutorial: if Statements
Related lessons
Related terms
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.
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)
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 timesenumerate(seq), loop with an indexzip(a, b), loop over two sequences togetherbreak / 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
Related terms
for loop A loop that runs once for each item in a sequence.
for fruit in ["apple", "pear", "plum"]:
print(fruit.upper())
for i in range(3): # 0, 1, 2
print("row", i)
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 timesenumerate(seq), loop with an indexzip(a, b), loop two sequences togetherbreak / continue, stop early / skip an itemfor 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.
count = 0
while count < 3: # check before each round
print("tick", count)
count += 1 # move toward the stop condition
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 Truebreak, leave the loop immediatelycontinue, skip to the next checkwhile True: … break, loop until you decide to stop
Official documentation: Python Language Reference: The while statement
Related lessons
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.
try:
number = int("not a number") # this raises ValueError
except ValueError:
number = 0 # handle it instead of crashing
print(number)
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 failuresexcept ValueError, catch one specific kind of errorelse / finally, run on success / always run (cleanup)raise, signal an error yourselfValueError, KeyError, FileNotFoundError, common built-in types
Official documentation: Python Tutorial: Errors and Exceptions
Related lessons
Related terms
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.
input_value = "oops"
try:
age = int(input_value) # raises ValueError
except ValueError:
age = 0 # handle it
finally:
print("done") # always runs
print(age)
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 failuresexcept ValueError as e, catch a type and inspect itexcept (A, B), catch several types at onceelse:, run only if no errorfinally:, always run (cleanup)raise, re-raise or signal an error
Official documentation: Python Tutorial: Handling Exceptions
Related lessons
Related terms
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.
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)
error: ValueError: invalid literal for int() with base 10: 'not-a-year' chain: <module> -> load -> parse_year deepest: parse_year
Official documentation: Python docs: traceback — print or retrieve a stack traceback
Related lessons
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.
def greet(name): # "name" is a parameter
return "Hello, " + name + "!"
message = greet("Sam") # call it with an argument
print(message)
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 argumentsdef total(x: int) -> int, type hints document inputs and outputreturn, hand a value back (no return means None)
Official documentation: Python Tutorial: Defining Functions
Related lessons
Related terms
Parameter A named variable in a function definition that receives an input value.
def greet(name, greeting="Hello"): # two parameters; greeting has a default
return greeting + ", " + name
print(greet("Sam"))
print(greet("Sam", "Hi"))
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 parametersdef f(a, b=10), a default value makes it optionaldef f(*args), accept any number of positional valuesdef f(**kwargs), accept any number of keyword valuesdef f(a: int), type-hint a parameter
Official documentation: Python Glossary: parameter
Related lessons
Related terms
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.
def make_user(name, admin=False):
return {"name": name, "admin": admin}
print(make_user("Ada")) # positional
print(make_user("Bo", admin=True)) # keyword argument
{'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 orderf(name='Sam'), keyword argument, matched by namef(*my_list), unpack a list into positional argumentsf(**my_dict), unpack a dict into keyword arguments
Official documentation: Python Glossary: argument
Related lessons
Related terms
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.
def total(prices):
return sum(prices) # hand the result back
bill = total([3, 5, 2]) # store the returned value
print(bill)
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 backreturn a, b, return several values as a tuplereturn, exit early, handing back Noneresult = f(), capture what a function returns
Official documentation: Python Language Reference: The return statement
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.
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)
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 functionsorted(items, key=lambda r: r['age']), sort by a computed keymax(items, key=lambda x: len(x)), pick by a computed value
Official documentation: Python Tutorial: Lambda Expressions
Related lessons
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.
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"))
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 belowfunctools.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 lessons
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.
def count_up_to(limit):
n = 1
while n <= limit:
yield n
n += 1
for number in count_up_to(3):
print(number)
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 pausenext(gen), pull the next value(x for x in seq), a generator expressionimport itertools, ready-made generator building blocks
Official documentation: Python Tutorial: Generators
Related lessons
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().
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))
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 iterablenext(it), pull the next value (StopIteration at the end)for x in it, the loop that uses them automaticallyimport itertools, chain, islice, count, and friends
Official documentation: Python Glossary: Iterator
Related lessons
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.
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)
hello-world Turn a title into a URL slug. shout has a docstring: False
Official documentation: PEP 257: Docstring Conventions
Related lessons
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.
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())
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 instanceself, the current instance inside a methodAccount(100), call the class to create an objectisinstance(obj, Account), check an object’s type
Official documentation: Python Tutorial: Classes
Related lessons
Related terms
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.
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))
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 objectisinstance(obj, Cls), is it that type (or a subclass)dir(obj), list its attributes and methodsvars(obj), its instance data as a dict
Official documentation: Python Language Reference: Objects, values and types
Related lessons
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.
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)
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 dotself, 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
Related lessons
Related terms
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.
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)
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 directlygetattr(obj, 'x', default), read by name, with a fallbacksetattr(obj, 'x', value), set by namehasattr(obj, 'x'), check whether it exists
Official documentation: Python Tutorial: Class and Instance Variables
Related lessons
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.
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))
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 Vehiclesuper().__init__(...), run the parent’s setup toodef describe(self), override a method to specialize it
Official documentation: Python Tutorial: Inheritance
Related lessons
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__.
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)
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
Official documentation: Python Language Reference: Special method names
Related lessons
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.
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)
Ada refused: name cannot be blank
Official documentation: Python HOWTO: descriptors
Related lessons
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.
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)}")
Basket sized: True list sized: True Switch sized: False
Official documentation: Python docs: typing.Protocol
Related lessons
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.
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__)
defined: ['circle', 'square'] type of Square: Registry
Official documentation: Python docs: metaclasses
Related lessons
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.
from pathlib import Path
p = Path("photos") / "vacation.JPG"
print(p.name)
print(p.suffix.lower())
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 lessons
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.
from pathlib import Path
p = Path("data") / "reports" / "june.csv"
print(p.name) # june.csv
print(p.suffix) # .csv
print(p.parent) # data/reports
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 pathPath.home(), Path.cwd(), home and current folders
Official documentation: Python Library Reference: pathlib
Related lessons
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.
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)
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 modulefrom pathlib import Path, import just one nameimport numpy as np, import under a shorter aliasdir(module), list what a module offershelp(thing), read its built-in documentation
Official documentation: Python Tutorial: Modules
Related lessons
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.
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]))
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 modulefrom module import name, import one nameimport module as alias, import under a shorter namefrom module import *, import everything (usually avoid)
Official documentation: Python Language Reference: The import system
Related lessons
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.
import sys
# Run as: python greet.py Ada
print(sys.argv) # ["greet.py", "Ada"]
print(sys.argv[1]) # "Ada" — arguments start at index 1
$ 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 argumentssys.argv[1:], everything after the script nameargparse, the standard way to define real flags and help
Official documentation: Python Library Reference: sys.argv
Related lessons
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.
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)
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 argumenttype=int, default=..., convert and supply a fallbackaction='store_true', an on/off flag.parse_args(), read the arguments into an object
Official documentation: Python Library Reference: argparse
Related lessons
Related terms
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.
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
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 Pythonjson.dumps(obj), turn Python into JSON textjson.load(file), read JSON from a filejson.dump(obj, file), write JSON to a fileindent=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.
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")
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 listscsv.DictReader(f), read rows as dicts by headercsv.writer(f), write rowscsv.DictWriter(f, fieldnames), write dicts as rowsnewline='', 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__.
with open("notes.txt", "w") as f: # opened here
f.write("hello")
# file is closed automatically here, even on error
print("saved")
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 filewith a, b:, manage several resources at oncecontextlib.contextmanager, write one from a generator__enter__ / __exit__, the methods that define one
Official documentation: Python Language Reference: Context managers
Related lessons
Related terms
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.
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 foldersource .venv/bin/activate, activate it (macOS/Linux)deactivate, step back outwhich python, confirm which Python is active
Official documentation: Python Library Reference: venv
Related lessons
Related terms
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.
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 packagepip install requests==2.31.0, install a specific versionpip uninstall requests, remove itpip list / pip freeze, see what’s installedpip install -r requirements.txt, install from a list
Official documentation: Python Documentation: Installing Python Modules
Related lessons
Related terms
Package A folder of related modules you can import; also a library you install with pip.
# 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 PyPIimport name, use an installed packagefrom pkg import module, import a package’s modulepip show name, see a package’s details
Official documentation: Python Tutorial: Packages
Related lessons
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.
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)))
asked for : reportmaker also gets : ['click', 'jinja2', 'markupsafe'] direct : 2 -> total 3
Official documentation: Python Packaging: managing application dependencies
Related lessons
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.
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 packagespip install -r requirements.txt, install them allrequests==2.31.0, pin a version for reproducibility
Official documentation: pip Documentation: Requirements File Format
Related lessons
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.
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 foldergit add / git commit, stage and save a checkpointgit status / git log, see changes and historygit diff, see exactly what changed
Official documentation: Git Documentation
Related lessons
Related terms
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.
def clean(name):
return name.strip().lower()
# Quick checks before trusting it:
assert clean(" Ada ") == "ada"
assert clean("BO") == "bo"
print("All tests passed")
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 checkpytest, find and run test filesdef test_thing():, pytest collects functions named test_*pytest.raises(ValueError), assert that an error is raised
Official documentation: Python Library Reference: unittest
Related lessons
Related terms
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.
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)
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
Related lessons
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.
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"])
name: tidyup version: 0.1.0 needs python: >=3.10 dependencies: ['rich>=13.0'] command: tidyup -> tidyup.cli:main
Official documentation: Python Packaging User Guide: writing your pyproject.toml
Related lessons
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.
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}))
module: json
function: dumps
calling: {"packaged": true}
Official documentation: Python Packaging User Guide: entry points specification
Related lessons
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.
# 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)
checkout: ok install: ok ruff check: FAILED failed at: ruff check exit code: 1
Official documentation: GitHub Actions docs: building and testing Python
Related lessons
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.
# 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}")
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
Related lessons
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.
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"]))
name: tests versions: ['3.11', '3.12'] keys: ['name', True, 'versions', 'debug'] debug is: False
Official documentation: PyYAML documentation
Related lessons
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.
# 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 functionrender_template('page.html'), fill an HTML templaterequest, read the incoming requestreturn, the response sent back to the browser
Official documentation: Flask Documentation
Related lessons
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.
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 data200, 404, 500, OK, not found, server errorheaders, extra info like content typerequests.get(url), make an HTTP request from Python
Official documentation: MDN Web Docs: HTTP
Related lessons
Related terms
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.
# 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 APIrequests.post(url, json=...), send data.status_code, 200 OK, 404 not found, and so on.json(), parse the JSON responseparams={…}, 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.
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 functionmethods=['GET', 'POST'], accept form submissions too/user/<id>, capture part of the URL as a value
Official documentation: Flask Documentation: Routing
Related lessons
Related terms
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.
<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 conditionallyurl_for('static', filename=...), build links safely
Official documentation: Flask Documentation: Rendering Templates
Related lessons
Related terms
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.
<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 URLname='email', the key your server reads the value byrequest.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
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.
<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 liveurl_for('static', filename='main.css'), build a static URLlink / script / img, the tags that load them
Official documentation: Flask Documentation: Static Files
Related lessons
Related terms
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.
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")
one at a time: 2.0s 5 at a time: 0.4s
Official documentation: Python docs: concurrent.futures
Related lessons
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.
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()))
coroutine Hello, Ada! ['Hello, Ada!', 'Hello, Sam!']
Official documentation: Python docs: Coroutines and Tasks
Related lessons
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.
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)
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.
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])
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
Related lessons
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.
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"])
you are in a dim room you take the lamp goodbye bag: ['lamp']
Official documentation: Python docs: tkinter — Python interface to Tcl/Tk
Related lessons
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.
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])
X shape: (3, 2) -> 3 examples, 2 features each y shape: (3,) -> one label per example first example: [50 1] -> 150
Official documentation: scikit-learn docs: preprocessing data
Related lessons
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.
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")
one at a time: 2.2s 4 threads: 2.2s
Official documentation: Python docs: Global interpreter lock
Related lessons
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.
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")
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.
# 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")
human guess per m2: 2.7 learned from data: 2.766 they disagree by: 5.9 on a 90m2 house
Official documentation: scikit-learn docs: an introduction to machine learning
Related lessons
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.
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))])
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.
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)
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]
Official documentation: NumPy docs: absolute beginner's guide
Related lessons
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.
# 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.
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))
straight train: 0.78 | test: 2.75 wiggly train: 0.53 | test: 14.95
Official documentation: scikit-learn docs: underfitting vs overfitting
Related lessons
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.
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])
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.
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)
after two increments: 1 should have been: 2
Official documentation: Python docs: Lock objects
Related lessons
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.
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))
['E404'] 2026-07-27 ERROR
Official documentation: Python docs: Regular Expression HOWTO
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.
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])
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
Related lessons
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.
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])
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.
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))
train on: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'] test on: ['i', 'j'] nothing appears in both: True
Official documentation: scikit-learn docs: cross-validation and evaluation
Related lessons
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.
def label(count: int) -> str:
return f"{count} items"
print(label(3))
print(label("three")) # wrong type, but Python does not object
3 items three items
Official documentation: Python docs: typing — support for type hints