Python Fundamentals
A practical reference for beginners and returning programmers
Eighteen short chapters covering syntax, data structures, control flow, functions, files, errors
and classes — with runnable examples throughout.
Part Chapters What it covers
I. Getting started 1–3 Installing Python, running code, variables and data types
II. Core data 4–8 Strings, numbers, lists, tuples, dictionaries and sets
III. Control flow 9 – 11 Conditionals, loops, comprehensions
IV. Structure 12 – 15 Functions, modules, exceptions, file handling
V. Objects & beyond 16 – 18 Classes, the standard library, common mistakes
1. Installing Python and running your first program
Python is distributed free from [Link]. On Windows, download the installer and tick Add
[Link] to PATH before continuing — skipping that box is the single most common cause of the
"python is not recognised" error later on. macOS ships with an older system Python that you should
leave alone; install a current version separately. Most Linux distributions already include Python 3, and
you can add the package manager with sudo apt install python3-pip or your distribution's
equivalent.
Confirm the installation from a terminal:
python --version
# Python 3.13.2
python3 --version # on macOS and Linux, python3 is usually the correct name
There are three ways to run code, and you will use all of them.
• The interactive shell. Type python with no arguments and you get a >>> prompt that evaluates
one line at a time. Ideal for testing a single idea. Exit with exit().
• Script files. Save your code in a file ending in .py and run python [Link]. This is how
real programs are written.
• Notebooks and editors. VS Code, PyCharm and Jupyter all wrap the same interpreter in a
friendlier interface. None of them change the language.
A complete first program is one line:
print("Hello, world!")
Comments start with # and run to the end of the line. The interpreter ignores them entirely, so use
them to explain why code does something, not to restate what it does — the code already says that.
Python Fundamentals — A Practical Reference Page 1
2. Variables and assignment
A variable is a name bound to a value. You do not declare a type; the value carries its own type and
the name simply points at it. Assignment uses a single equals sign, and rebinding a name to a different
type is legal.
count = 42
count = "forty-two" # perfectly legal, though usually a bad idea
x, y = 3, 7 # multiple assignment
x, y = y, x # swap without a temporary variable
total = subtotal = 0 # chained assignment: both names point at 0
Names may contain letters, digits and underscores, but cannot begin with a digit. Python is
case-sensitive, so total and Total are different variables. The community convention, set out in the
PEP 8 style guide, is lower_snake_case for variables and functions, CapWords for classes, and
SCREAMING_SNAKE_CASE for constants.
Reserved words. You cannot use a keyword as a variable name. The full list is short: False, None, True,
and, as, assert, async, await, break, class, continue, def, del, elif, else, except, finally, for, from, global, if,
import, in, is, lambda, nonlocal, not, or, pass, raise, return, try, while, with, yield. Shadowing a built-in such
as list or sum is allowed but will cause confusing bugs — avoid it.
3. The core data types
Every value in Python has a type, which you can inspect with the built-in type() function. These six
cover the overwhelming majority of everyday code.
Type Example literal Notes
int 42, -7, 1_000_000 Unlimited precision; underscores are ignored and aid readability
float 3.14, 2.0, 1e-9 64-bit binary floating point; subject to rounding error
str "hello", 'hello' Immutable sequence of Unicode characters
bool True, False A subclass of int, so True == 1 evaluates to True
list [1, 2, 3] Ordered, mutable, allows duplicates
dict {"a": 1} Key–value mapping, insertion-ordered since Python 3.7
Conversion between types is explicit, which is why Python is called strongly typed:
int("42") + 1 # 43
str(42) + "!" # "42!"
float("3.5") # 3.5
int(3.99) # 3 — truncates toward zero, does not round
bool(""), bool("a") # (False, True)
"42" + 1 # TypeError: can only concatenate str (not "int") to str
Python Fundamentals — A Practical Reference Page 2
None is a distinct value meaning "no value here". It is what a function returns when it has no explicit
return statement. Test for it with if x is None, never with == None.
4. Working with strings
Strings are immutable: every operation that appears to modify one actually returns a new string. They
can be written with single or double quotes interchangeably, and triple quotes span multiple lines.
name = "Ada Lovelace"
len(name) # 12
[Link]() # "ADA LOVELACE"
[Link]() # "ada lovelace"
[Link]() # ["Ada", "Lovelace"]
[Link]("a", "@") # "Ad@ Lovel@ce" — original 'name' is unchanged
" spaced ".strip() # "spaced"
"-".join(["a","b","c"])# "a-b-c"
[Link]("Ada") # True
"love" in [Link]() # True
Indexing and slicing
Positions are zero-based, and negative indices count backwards from the end. A slice
[start:stop:step] includes the start position and excludes the stop position — a rule that applies
consistently to every sequence type in the language.
s = "PYTHON"
s[0] # "P" first character
s[-1] # "N" last character
s[1:4] # "YTH" positions 1, 2, 3
s[:3] # "PYT" from the beginning
s[3:] # "HON" to the end
s[::2] # "PTO" every second character
s[::-1] # "NOHTYP" reversed
f-strings
Formatted string literals, introduced in Python 3.6, are the modern way to build strings. Prefix the literal
with f and put expressions in braces. Format specifiers after a colon control width, precision and
alignment.
item, price, qty = "Coffee", 4.5, 3
f"{qty} x {item} = {qty * price}" # "3 x Coffee = 13.5"
f"Total: {qty * price:.2f}" # "Total: 13.50"
f"{item:>10}|" # " Coffee|" right-aligned in 10 columns
f"{0.8734:.1%}" # "87.3%"
f"{1234567:,}" # "1,234,567"
f"{price=}" # "price=4.5" handy for debugging
Python Fundamentals — A Practical Reference Page 3
5. Numbers and arithmetic
Operator Meaning Example Result
+ - * Add, subtract, multiply 7*3 21
/ True division (always float) 7/2 3.5
// Floor division 7 // 2 3
% Modulo (remainder) 7%2 1
** Exponent 2 ** 10 1024
Augmented assignment combines an operation with assignment: total += 5 is shorthand for total
= total + 5, and the same pattern works for -=, *=, /= and the rest.
Floating point is approximate. 0.1 + 0.2 evaluates to 0.30000000000000004, not 0.3. This is not a
Python bug — it is how binary floating point works in every language. For money and other exact decimal
work, use from decimal import Decimal and construct values from strings: Decimal("0.1").
6. Lists
A list is an ordered, mutable collection. It is the workhorse container of Python and can hold values of
mixed types, though in practice a list usually holds one kind of thing.
fruits = ["apple", "banana", "cherry"]
[Link]("date") # add one item to the end
[Link](0, "apricot") # insert at a position
[Link](["elderberry", "fig"]) # add several
[Link]("banana") # delete by value (first match only)
last = [Link]() # remove and return the final item
[Link]() # sort in place, alphabetically
[Link](reverse=True) # descending
[Link]() # flip the current order
len(fruits) # number of items
"cherry" in fruits # membership test -> True or False
[Link]("cherry") # position of the first match
[Link]("fig") # how many times it appears
Copying versus aliasing
Assigning a list to a new name does not copy it — both names refer to the same object, so a change
through one is visible through the other. This catches out almost every beginner.
a = [1, 2, 3]
b = a # b is an alias, NOT a copy
[Link](4)
print(a) # [1, 2, 3, 4] — a changed too
c = [Link]() # a genuine shallow copy (or list(a), or a[:])
[Link](5)
print(a) # [1, 2, 3, 4] — unaffected
Python Fundamentals — A Practical Reference Page 4
sorted(a) returns a new sorted list and leaves the original alone, whereas [Link]() rearranges in
place and returns None. Writing a = [Link]() is a frequent mistake that quietly replaces your list
with None.
7. Tuples and sets
A tuple is an immutable sequence, written with parentheses. Because it cannot change, it is safe to
use as a dictionary key and signals to a reader that the grouping is fixed — coordinates, RGB colours,
database rows.
point = (3, 7)
x, y = point # unpacking
single = (5,) # the trailing comma is what makes it a tuple
point[0] = 9 # TypeError: 'tuple' object does not support item assignment
A set is an unordered collection of unique values. Membership testing is very fast, and duplicates are
discarded automatically, which makes sets the natural tool for de-duplication and comparison.
a = {1, 2, 3, 3, 3} # {1, 2, 3}
b = {3, 4, 5}
a | b # {1, 2, 3, 4, 5} union
a & b # {3} intersection
a - b # {1, 2} difference
a ^ b # {1, 2, 4, 5} symmetric difference
unique = list(set(["a", "b", "a"])) # de-duplicate, order not preserved
empty = set() # {} would create an empty dictionary instead
8. Dictionaries
A dictionary maps keys to values. Keys must be immutable (strings, numbers and tuples are typical);
values can be anything. Since Python 3.7 dictionaries preserve insertion order.
person = {"name": "Ada", "born": 1815, "field": "mathematics"}
person["name"] # "Ada"
person["email"] # KeyError — the key does not exist
[Link]("email") # None — safe lookup
[Link]("email", "n/a") # "n/a" — with a fallback
person["email"] = "ada@[Link]" # add or overwrite
del person["born"] # remove a key
[Link]("field", None) # remove, returning a default if absent
[Link]() # view of the keys
[Link]() # view of the values
[Link]() # view of (key, value) pairs
"name" in person # True — checks keys, not values
Iterating over a dictionary yields its keys, so unpack .items() when you need both halves:
Python Fundamentals — A Practical Reference Page 5
scores = {"alice": 91, "bob": 78, "cara": 84}
for name, score in [Link]():
print(f"{name:<8} {score:>3}")
best = max(scores, key=[Link]) # "alice"
average = sum([Link]()) / len(scores)
ranked = sorted([Link](), key=lambda pair: pair[1], reverse=True)
Need Reach for Because
Ordered items that change list Cheap appends, indexable, sortable
A fixed grouping of values tuple Immutable, hashable, self-documenting
Unique values, fast membership
set Automatic de-duplication, O(1) lookups
Lookup by name or id dict Direct key access rather than scanning
9. Conditionals
Python uses indentation rather than braces to mark a block. Four spaces per level is the convention;
mixing tabs and spaces raises an error. The colon at the end of the condition line is required.
temperature = 31
if temperature > 30:
print("Hot")
elif temperature > 20:
print("Pleasant")
elif temperature > 10:
print("Cool")
else:
print("Cold")
status = "Hot" if temperature > 30 else "Not hot" # conditional expression
Comparison operators are ==, !=, <, <=, >, >=, and they chain naturally: 0 <= score <= 100 reads
exactly as it does in mathematics. Combine conditions with and, or and not rather than symbols.
Truthiness
Any value can be tested directly. Empty containers, empty strings, zero and None are falsy; everything
else is truthy. So if items: is the idiomatic way to ask "is this list non-empty?" — clearer than if
len(items) > 0:.
== compares values, is compares identity. Two separate lists containing the same items are == but not
is. Reserve is for comparisons against None, True and False.
10. Loops
A for loop iterates over the items of any sequence directly. There is no need to manage an index
counter, and doing so is considered unidiomatic.
Python Fundamentals — A Practical Reference Page 6
for fruit in ["apple", "banana", "cherry"]:
print(fruit)
for i in range(5): # 0, 1, 2, 3, 4
print(i)
for i in range(2, 11, 2): # 2, 4, 6, 8, 10 — start, stop, step
print(i)
for index, fruit in enumerate(fruits, start=1):
print(index, fruit) # enumerate when you genuinely need the position
for name, score in zip(names, scores):
print(name, score) # zip walks two sequences in parallel
A while loop repeats as long as its condition holds. Use it when the number of iterations is not known
in advance, and make certain something inside the body can eventually make the condition false.
balance = 1000
years = 0
while balance < 2000:
balance *= 1.07
years += 1
print(f"Doubled after {years} years")
break exits the innermost loop immediately; continue skips to the next iteration. Both should be
used sparingly, since a loop with several exit points is harder to reason about.
11. Comprehensions
A comprehension builds a new collection from an existing iterable in a single expression. It is the most
recognisably Pythonic construct in the language, and once the shape is familiar it reads more clearly
than the equivalent loop.
numbers = [1, 2, 3, 4, 5, 6]
squares = [n ** 2 for n in numbers] # [1, 4, 9, 16, 25, 36]
evens = [n for n in numbers if n % 2 == 0] # [2, 4, 6]
labelled = [f"#{n}" for n in numbers if n > 4] # ["#5", "#6"]
lookup = {word: len(word) for word in ["hi", "there"]} # {"hi": 2, "there": 5}
initials = {name[0] for name in ["Ada", "Alan", "Grace"]} # {"A", "G"}
Keep comprehensions to one condition and one level of nesting. Beyond that, a plain loop is kinder to
whoever reads the code next — including you in six months.
12. Functions
A function packages a piece of behaviour behind a name so it can be reused and tested. Define one
with def, and return a value with return; a function without a return statement returns None.
Python Fundamentals — A Practical Reference Page 7
def greet(name, greeting="Hello"):
"""Return a greeting for the given name."""
return f"{greeting}, {name}!"
greet("Ada") # "Hello, Ada!"
greet("Ada", "Good morning") # positional argument
greet(name="Ada", greeting="Hi") # keyword arguments, order no longer matters
Parameters with defaults must come after those without. *args collects any extra positional
arguments into a tuple and **kwargs collects extra keyword arguments into a dictionary.
def total(*numbers, label="Sum"):
return f"{label}: {sum(numbers)}"
total(1, 2, 3) # "Sum: 6"
total(1, 2, 3, label="Total") # "Total: 6"
Scope
Names created inside a function are local to it and disappear when the call ends. A function can read
names from the enclosing module, but assigning to one creates a new local name instead of modifying
the outer one. Rather than reaching for the global keyword, pass values in as arguments and send
results back with return — code written that way is far easier to test.
Never use a mutable default argument. Writing def add(item, basket=[]) creates the list once,
when the function is defined, so it is shared across every call and quietly accumulates items. Use
basket=None and build a fresh list inside the body.
13. Errors and exceptions
When something goes wrong, Python raises an exception and prints a traceback. Read a traceback
from the bottom up: the last line names the error type and message, and the lines above show the path
of calls that led there.
Exception Typical cause
SyntaxError A typo the interpreter cannot parse — a missing colon, bracket or quote
IndentationError Inconsistent indentation, often mixed tabs and spaces
NameError Using a variable before assigning it, or a misspelt name
TypeError An operation applied to the wrong type, such as adding a str to an int
ValueError Right type, unusable value — int("abc")
IndexError A sequence index beyond the end of the sequence
KeyError A dictionary key that does not exist
FileNotFoundError Opening a path that is not there, usually a working-directory issue
ZeroDivisionError Dividing by zero
Python Fundamentals — A Practical Reference Page 8
Handle the failures you can anticipate with try, catching specific exception types rather than
everything:
try:
value = int(user_input)
except ValueError:
print("That was not a whole number.")
else:
print(f"Thanks — {value} accepted.") # runs only if no exception occurred
finally:
print("Done.") # always runs, error or not
raise ValueError("Quantity must be positive") # signal an error yourself
A bare except: swallows everything, including typos in your own code and the keyboard interrupt you
press to stop the program. Catch the narrowest exception that fits, and let anything unexpected
surface.
14. Reading and writing files
Always open files with a with block. It closes the file automatically at the end of the block, even if an
exception is raised part-way through.
with open("[Link]", "r", encoding="utf-8") as f:
contents = [Link]() # the whole file as one string
with open("[Link]", encoding="utf-8") as f:
for line in f: # memory-efficient: one line at a time
print([Link]())
with open("[Link]", "w", encoding="utf-8") as f:
[Link]("First line\n") # "w" truncates any existing file
[Link](["a\n", "b\n"])
with open("[Link]", "a", encoding="utf-8") as f:
[Link]("appended\n") # "a" adds to the end instead
Specify encoding="utf-8" explicitly. Relying on the platform default is the usual reason a file that
opens cleanly on one machine produces garbled characters on another. For structured data, the
standard library already has readers: csv for spreadsheets and json for nested records.
import json
with open("[Link]", encoding="utf-8") as f:
config = [Link](f) # JSON object -> Python dict
with open("[Link]", "w", encoding="utf-8") as f:
[Link](config, f, indent=2)
15. Modules and imports
Any .py file is a module, and the standard library gives you hundreds of them for free. Import at the
top of the file.
Python Fundamentals — A Practical Reference Page 9
import math
from datetime import date, timedelta
from collections import Counter
import statistics as stats # alias for brevity
[Link](144) # 12.0
[Link]() + timedelta(days=30)
Counter("mississippi").most_common(2) # [("i", 4), ("s", 4)]
[Link]([3, 1, 4, 1, 5]) # 3
Third-party packages install with pip: pip install requests. Create a virtual environment per
project so that dependencies stay isolated — python -m venv .venv, then activate it and install
inside. Without one, every project shares the same libraries and upgrading for one breaks another.
The __name__ guard at the bottom of a script lets a file act both as an importable module and as a
runnable program:
def main():
print("Running as a program")
if __name__ == "__main__":
main()
16. Classes in brief
A class bundles data with the functions that operate on it. You will use classes written by other people
long before you need to write your own, but the syntax is worth recognising.
class BankAccount:
def __init__(self, owner, balance=0):
[Link] = owner # instance attributes
[Link] = balance
def deposit(self, amount):
if amount <= 0:
raise ValueError("Deposit must be positive")
[Link] += amount
return [Link]
def __repr__(self):
return f"BankAccount({[Link]!r}, {[Link]})"
account = BankAccount("Ada", 100)
[Link](50) # 150
print(account) # BankAccount('Ada', 150)
__init__ runs when an instance is created. self is the instance itself and must be the first
parameter of every method, though you never pass it explicitly. For classes that only carry data, from
dataclasses import dataclass generates the boilerplate for you.
Python Fundamentals — A Practical Reference Page 10
17. Built-in functions worth memorising
Function What it does Example
len(x) Number of items len("hello") -> 5
range(a, b, s) Sequence of integers list(range(0, 6, 2)) -> [0, 2, 4]
enumerate(x) Pairs of index and item for i, v in enumerate(items)
zip(a, b) Walks sequences in parallel dict(zip(keys, values))
sorted(x, key=) New sorted list sorted(words, key=len)
sum / min / max Aggregate a sequence max([Link]())
any / all Boolean over a sequence all(n > 0 for n in nums)
abs / round Magnitude and rounding round(3.567, 2) -> 3.57
isinstance(x, T) Type check isinstance(x, str)
input(prompt) Read a line as a string age = int(input("Age? "))
18. Ten mistakes that cost beginners the most time
• Confusing = with ==. One assigns, the other compares. In a condition you almost always want the
double equals.
• Forgetting that input() returns a string. Convert with int() or float() before doing arithmetic,
and wrap the conversion in try/except.
• Modifying a list while looping over it. Removing items shifts the positions underneath the iterator
and silently skips entries. Build a new list instead.
• Assuming assignment copies. For lists, dictionaries and sets it does not — use .copy(), or
[Link]() for nested structures.
• Mutable default arguments. The default is created once and shared by every call.
• Naming a file after a module. Saving your script as [Link] means import random finds
your file instead of the library.
• Comparing floats for exact equality. Use [Link](a, b) to allow for representation
error.
• Catching every exception. A bare except hides real bugs; name the exception you expect.
• Off-by-one with ranges and slices. The stop value is always excluded — range(1, 5) yields
four numbers, not five.
• Not reading the traceback. The last line names the error and the line number. It is nearly always
the fastest route to the answer.
Python Fundamentals — A Practical Reference Page 11
Where to go next
Work through a small project rather than more tutorials: a script that renames files in a folder, a
command-line to-do list stored in JSON, a program that reads a CSV of expenses and prints a monthly
summary. Each one forces you to combine loops, functions, dictionaries and error handling in a way
that reading alone never will.
From there, the natural next topics are virtual environments and dependency management, writing
tests with pytest, type hints, and whichever domain library matches your interest — pandas for
tabular data, requests for web APIs, Flask or FastAPI for web services. The official tutorial at
[Link] remains the most reliable reference, and every function mentioned here is
documented there in full.
Reference document · Python 3.13 · Prepared with Python examples verified against the standard library. Free
to share and reuse.
Python Fundamentals — A Practical Reference Page 12