Learn Python 2026
A free, structured curriculum
A full-spectrum Python education from absolute beginner to real-world automation. 950+ runnable examples, 18 topic modules, written to be read start to finish or used as a reference.
def greet(name: str) -> str: return f"Hello, {name}! Python is 🐍" learners = ["Ana", "Carlos", "Samuel"] for learner in learners: print(greet(learner))
Hello, Ana! Python is 🐍
Hello, Carlos! Python is 🐍
Hello, Samuel! Python is 🐍
📖 Welcome to the Complete Python Learning Journey
This is a structured, free, and interactive curriculum designed to take you from absolute beginner to a confident Python developer — organised the way I wish a single resource had been organised when I was learning.
📜 The Python 2026 Curriculum — What You Will Learn
This curriculum is sequenced from first principles to production‑grade patterns, covering not just syntax but the why behind every feature — the data model, the GIL, memory management, the type system, and the design philosophy that makes Python readable by design rather than by accident.
You will write and run real code from lesson one, using Pyodide in your browser — no setup required. Every module is paired with runnable examples, step‑by‑step explanations, and analogies chosen to make abstract topics concrete. You'll build a small portfolio along the way: CLI tools, web scrapers, data visualisations, REST APIs, and AI‑powered applications.
By the end, the goal is that you think in Python — not just write it: understanding how to design robust systems, debug with confidence, and read other people's code without getting lost. This is meant as a solid foundation for data science, backend engineering, DevOps, or any field where Python shows up.
🚀 What This Curriculum Covers
Python is used across an unusually wide range of work — AI research, office automation scripts, machine-learning pipelines, backend services. This curriculum walks through the language systematically so you're not left guessing which parts matter.
We cover 18 topic modules, sequenced from fundamentals to advanced:
- 🐍 Python Basics – Variables, data types, operators, control flow, loops, and functions — the foundation of everything.
- 📦 Data Structures – Strings, lists, dictionaries, tuples, and sets — the tools for organising data.
- 🏛️ Object‑Oriented Programming – Classes, inheritance, polymorphism, encapsulation, and the dunder methods that make Python objects behave predictably.
- ⚡ Advanced Python – Generators, decorators, context managers, async/await, type hints, and static analysis with mypy.
- 📁 File I/O & Modules – Read/write files, use pathlib, structure projects with packages, and handle exceptions properly.
- 🚀 Applied Domains – Web scraping, data analysis (pandas, NumPy, Matplotlib), AI (LLM APIs, NLP), machine learning (scikit‑learn, TensorFlow), and more.
Every concept is accompanied by runnable code you can execute directly in your browser using Pyodide, without installing anything. Step‑by‑step explanations break down each block so you're not left studying syntax in isolation.
🤔 Why This Curriculum Is Organised This Way
Python's Reach
Python shows up across nearly every domain of modern software — web backends, data pipelines, ML deployment, and everyday automation. Understanding it well opens doors across all of these.
Where Beginners Get Stuck
Not understanding Python's quirks tends to cause specific, avoidable problems:
- Floating‑point rounding errors in numeric code
- Memory‑hungry code that slows down or crashes on larger inputs
- Concurrency bugs that are hard to reproduce
- Bugs from mutable default arguments or unclear type handling
This curriculum tries to explain the why behind each feature, so these problems are recognisable before they cause real trouble.
The Standard Library as a Starting Point
Python's "batteries included" philosophy gives you a genuinely useful standard library: math, random, datetime, collections, itertools, asyncio, and dozens more — all available without installing anything. Combined with the third‑party ecosystem on PyPI, this is often enough to build real tools without much external dependency.
Theory Alongside Practice
Beyond syntax, this curriculum explains why Python behaves the way it does — the data model, the GIL, memory management, the type system. That understanding is what lets you write idiomatic code and debug issues that would otherwise be mysterious.
Where Python Is Actually Used
- Data Science & AI – one of the most common languages in the field
- Backend Development – Flask, Django, and FastAPI back a large share of web services
- DevOps & Automation – Python scripts frequently glue infrastructure together
- Scientific Computing – widely used for simulations and analysis in research settings
- General Software Engineering – a solid, readable choice for many kinds of projects
🌟 On Numbers, Data, and Abstraction
Numbers as a Foundation
Every digital system ultimately relies on numbers. When you write code, you're manipulating symbols that represent numbers underneath. Understanding how these numbers are stored, manipulated, and transformed is understanding a basic layer of how software works.
Building Blocks of Abstraction
Numbers are the foundation other abstractions are built on. Strings are numbers in disguise (ASCII/Unicode). Lists are numbers with addresses. Dictionaries are numbers used as keys. Files are numbers stored as bytes. Every data structure ultimately decomposes into numbers, so getting comfortable with numeric operations pays off broadly.
Python's Numeric Design
- Arbitrary precision integers — no silent overflow, useful for large-scale computations
- Seamless mixing of numeric types in operations (with implicit conversion)
- A standard library that covers a lot of common mathematical needs
- Mature external libraries (NumPy, SciPy, Pandas) built on this foundation
Where This Leads
Understanding Python's fundamentals is what makes the following genuinely accessible later on:
- NumPy – Multi‑dimensional arrays and vectorised operations
- SciPy – Scientific computing and optimisation
- Pandas – Data analysis and manipulation
- Matplotlib – Data visualisation
- Scikit‑learn – Machine learning
- TensorFlow/PyTorch – Deep learning
Each of these libraries is built on the fundamental numeric and structural capabilities this curriculum covers early on.
Beyond Syntax
The most useful thing this curriculum can offer is an understanding of why things work the way they do. Knowing about the GIL helps you choose the right concurrency model. Knowing about duck typing helps you write more flexible code. Knowing about the data model helps you create objects that feel native to Python. That's the difference between code that happens to work and code that's designed to be correct and maintainable.
▶️ How to Run the Python Code
Every snippet on this page is ready to copy, paste, and run
Run Python in your browser — no install needed
All code examples target Python 3.10+. You can run them directly in your browser using the Run button on each snippet, or copy them to a local .py file and execute with python3.
Option 1 — Run in your browser (no setup)
Click the Run button below any code block — it executes inside your browser using Pyodide (Python compiled to WebAssembly).
- ✓ Works immediately, no installation
- ✓ First run loads the Python runtime (~10s), subsequent runs are instant
- ✓ Output appears below the snippet
- ✓ ⚠️ File operations run in a virtual filesystem — files don't persist. Copy code and run locally for real file work.
Option 2 — Run locally (full control)
Copy the snippet into a .py file and execute it with Python 3.10+.
Why Python Is Worth Learning
The language behind a lot of AI, data science, automation, and web work
Stack Overflow 2024
for nearly every domain
to cover the foundations
first released publicly
Python has become one of the most widely used programming languages of the last decade. From AI algorithms to office automation scripts to machine-learning models used in healthcare — Python is a common thread across a lot of modern software. Its guiding philosophy — readable code is better than clever code — means beginners tend to produce working programs quickly, while the language still has enough depth to keep experienced developers learning.
Poetic Bytes exists to give anyone motivated access to structured, honest, jargon-free Python education — for free.
- ✓Write clean, idiomatic Python scripts and automate repetitive tasks
- ✓Design well-structured classes using OOP: inheritance, encapsulation, polymorphism
- ✓Handle, clean, and visualise real-world datasets using Pandas and Matplotlib
- ✓Build and consume RESTful web APIs with Flask or Django
- ✓Write async code with
asynciofor high-performance I/O - ✓Organise larger projects with modules, packages, and virtual environments
- ✓Build a small portfolio of real projects along the way
📚 Complete Python Curriculum
18 topic modules — from your first variable to production AI
Variables, assignment, naming conventions, how Python executes code line by line. Zero prior experience needed.
Explore →Capture user input with input(), handle type conversion, validate data, and build interactive command-line programs.
Integers, floats, complex numbers. Arithmetic operators, precedence, the math module, Decimal.
Arithmetic, comparison, logical, assignment, bitwise, membership, identity operators, and the walrus operator.
Explore →if/elif/else, for, while, break, continue, and Python 3.10+ match statements.
Master for and while loops — iterating over ranges, lists, dicts, and custom iterables with break, continue, and else clauses.
Truthiness, falsiness, short-circuit evaluation, and bool() with different types.
f-strings, slicing, re (regex), multiline strings, built-in methods like .split(), .join().
Comprehensions, sorting, nested lists, append(), pop(), copy vs deepcopy.
Key-value stores with O(1) lookup. .get(), defaultdict, dict comprehensions, JSON mapping.
Immutable sequences. Packing and unpacking, named tuples, using tuples as dict keys.
Explore →Unordered unique collections. Set operations: union, intersection, difference. frozenset and set comprehensions.
Explore →*args, **kwargs, lambdas, closures, higher-order functions, decorators, recursion.
Define classes, write __init__, understand self, class vs instance variables, @property.
Organise code into reusable files and packages. import mechanics, __init__.py, relative imports, pip, and virtual environments.
Robust error handling with try/except/else/finally. Custom exceptions, exception chaining, and logging.
The iterator protocol, __iter__/__next__, generator functions with yield, generator expressions, and itertools.
The with statement, __enter__/__exit__, writing your own context managers with contextlib.
Bundle data and methods inside a class. Hide internals with _private, __mangled, and @property.
Expose only what's necessary. abc module, Abstract Base Classes, enforcing contracts between classes.
Reuse and extend class behaviour. MRO, super(), method overriding, multiple inheritance, mixins.
One interface, many forms. Duck typing, dunder methods, operator overloading, isinstance().
Write non-blocking code with asyncio. async def, await, aiohttp, tasks, event loops, and concurrent patterns.
Factory decorators, class decorators, stacking, functools.wraps, real-world patterns like caching, timing, and auth guards.
Annotate everything. TypeVar, Protocol, TypedDict, Literal, Final, generic classes, and running mypy in CI.
Read, write, append files. pathlib, CSV, JSON, binary files, shutil, os.path, and context managers.
Dedicated deep dive into reading and writing text, binary, and structured files with open(), pathlib, and safe file handling patterns.
Extract structured data from websites. requests, BeautifulSoup, Playwright, Selenium, and handling pagination.
NumPy, Pandas, Matplotlib, Seaborn. Clean, transform, and visualise real-world datasets.
Explore →LLM APIs (OpenAI, Anthropic), NLP, search algorithms, HuggingFace transformers, prompt engineering.
Explore →Supervised & unsupervised learning. scikit-learn, TensorFlow, PyTorch, neural networks.
Explore →Generate spoken audio from text using Python. Explore pyttsx3, Google TTS, Amazon Polly, and build a CLI text-to-speech tool.
Not sure where to start?
The modules are sequenced beginner to advanced, but feel free to jump in anywhere that matches your level.
✨ 6 New Modules — Just Added
Expand beyond basics with these essential intermediate and advanced topics
🔵 Sets — Fast Unique Collections
A set is an unordered collection of unique, hashable objects. It's the fastest Python data structure for membership testing and is ideal for deduplication, intersection, and difference operations.
def demonstrate_sets(): languages = {"Python", "JavaScript", "Rust", "Python"} print("Unique languages:", languages) backend = {"Python", "Go", "Rust"} frontend = {"JavaScript", "TypeScript", "Python"} print("Union:", backend | frontend) print("Intersection:", backend & frontend) print("Backend only:", backend - frontend) names = ["Alice", "Bob", "Alice", "Charlie", "Bob"] unique = list(set(names)) print("Deduplicated names:", unique) demonstrate_sets()
x in my_set is O(1). The same check on a list is O(n). For membership testing on large collections, always prefer a set.📦 Organising Code — Modules, Packages & Virtual Environments
As projects grow beyond a single file, Python's module system keeps code organised, testable, and shareable. A module is a single .py file. A package is a folder containing an __init__.py.
Example code removed to avoid import errors in the browser; run this locally with proper file structure.
python -m venv .venv as your first step on every new project.🛡️ Robust Exception Handling — Beyond try/except
Production Python code anticipates failure at every external boundary. The full try/except/else/finally structure handles each case cleanly. Custom exceptions communicate intent.
class AppError(Exception): pass class ValidationError(AppError): def __init__(self, field: str, message: str): self.field = field super().__init__(f"{field}: {message}") def parse_user_data(raw: str) -> dict: try: data = json.loads(raw) except json.JSONDecodeError as e: raise ValidationError("body", "Invalid JSON") from e else: return data finally: print("Parsing attempt finished")
raise NewError from original preserves the full traceback so you never lose the root cause while adding application-level context.⚡ Generators — Lazy, Memory-Efficient Iteration
A generator function uses yield instead of return. It produces values one at a time on demand — no memory is used to hold the full sequence. Essential for processing large files, infinite streams, and data pipelines.
def count_up(start: int, stop: int, step: int = 1): current = start while current < stop: yield current current += step for n in count_up(0, 10, 2): print(n, end=" ") print() squares = (x**2 for x in range(5)) print("Squares:", list(squares)) def read_large_file(path: str): with open(path, encoding="utf-8") as f: for line in f: yield line.strip()
🚀 asyncio — Non-Blocking Python
Synchronous code waits for each I/O operation to complete before starting the next. Async code schedules many I/O tasks concurrently, dramatically improving throughput for network-heavy workloads like web scraping, API calls, and database queries.
The asyncio example is omitted because Pyodide's event loop prevents calling asyncio.run(); run this code locally to see it in action.
asyncio for I/O-bound concurrency (network, files, databases). Use multiprocessing for CPU-bound work. Never block an async event loop with time.sleep() — use await asyncio.sleep() instead.🎨 Factory Decorators & Real-World Patterns
A decorator factory is a function that returns a decorator, allowing parameters. This pattern powers every framework you'll use — Flask routes, pytest fixtures, Django views, and more.
import functools, time def retry(times: int = 3, delay: float = 1.0): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): for attempt in range(1, times + 1): try: return func(*args, **kwargs) except Exception as e: if attempt == times: raise print(f"Attempt {attempt} failed: {e}. Retrying...") time.sleep(delay) return wrapper return decorator @retry(times=2, delay=0.5) def unstable_operation() -> str: import random if random.random() < 0.6: raise ValueError("Random failure!") return "Success!" print(unstable_operation())
functools.lru_cache and cache are Python's built-in memoization decorators — zero boilerplate caching for any pure function.🔍 Type Hints — Modern Python Annotations
Type hints document your code and enable static analysis with mypy or pyright. They don't change runtime behaviour but catch entire classes of bugs before the code runs.
from typing import TypeVar, Generic, Protocol from collections.abc import Sequence T = TypeVar("T") class Stack(Generic[T]): def __init__(self) -> None: self._items: list[T] = [] def push(self, item: T) -> None: self._items.append(item) def pop(self) -> T: return self._items.pop() stack: Stack[int] = Stack() stack.push(42) stack.push(100) print("Popped:", stack.pop()) class Drawable(Protocol): def draw(self) -> None: ... def render(shape: Drawable) -> None: shape.draw()
mypy --strict your_file.py to catch type errors before they reach production. Integrate it in GitHub Actions for automatic checking on every push.⚠️ 10 Python Mistakes Every Beginner Makes
Learn from thousands of learners — avoid the errors that waste the most time
Every Python beginner tends to make the same set of mistakes — not because they aren't smart, but because Python has a handful of genuinely surprising behaviours that differ from plain logic or from other programming languages. Recognising these traps early saves hours of frustrated debugging. Each mistake below comes with a clear explanation of why it happens and exactly how to fix it.
❌ Mistake 1 — Using a mutable default argument
Default argument values are evaluated once when the function is defined — not each time it is called. If you use a mutable object like a list as a default, all calls that use the default share the same object.
def add_item_bad(item, items=[]): items.append(item) return items print(add_item_bad("apple")) print(add_item_bad("banana")) def add_item_good(item, items=None): if items is None: items = [] items.append(item) return items print(add_item_good("apple")) print(add_item_good("banana"))
None as the default and create the mutable object inside the function body.❌ Mistake 2 — Confusing == with is
== checks whether two values are equal. is checks whether two names point to the exact same object in memory. For small integers and interned strings Python reuses objects, which can make is appear to work — until it silently does not.
a = 256 b = 256 print("256 is 256:", a is b) a = 257 b = 257 print("257 is 257:", a is b) print("257 == 257:", a == b) if result is None: print("Use 'is' for None comparisons")
❌ Mistake 3 — Modifying a list while iterating over it
Changing the size of a list during a for loop causes items to be skipped silently — one of the hardest bugs to spot because Python gives no error, it just produces wrong output.
nums = [1, 2, 3, 4, 5, 6] for n in nums: if n % 2 == 0: nums.remove(n) print("After bad removal:", nums) nums = [1, 2, 3, 4, 5, 6] nums = [n for n in nums if n % 2 != 0] print("After good removal:", nums)
❌ Mistake 4 — Catching too broad an exception
Writing bare except: swallows every error — including typos in variable names, import errors, and keyboard interrupts — making bugs nearly impossible to diagnose.
try: result = int("not a number") except ValueError: print("Please enter a valid number") except TypeError as e: print(f"Type error: {e}") finally: print("Cleanup always runs")
logging.exception(e) so the error is not lost.❌ Mistake 5 — Not understanding variable scope (LEGB rule)
Python resolves variable names in a specific order: Local → Enclosing → Global → Built-in. Forgetting this leads to confusing UnboundLocalError bugs.
count = 0 def increment_bad(): count += 1 def increment_good(): global count count += 1 def increment_pure(count): return count + 1 count = increment_pure(count) print("Count:", count)
❌ Mistake 6 — Using + to concatenate strings in a loop
Strings in Python are immutable. Every + creates a new string object and copies both into it. Inside a loop over thousands of items, this creates quadratic time complexity.
words = ["Python", "is", "fast", "and", "readable"] result = "" for word in words: result += word + " " print(result) result = " ".join(words) print(result)
❌ Mistake 7 — Forgetting integers and strings do not auto-convert
Python is strongly typed. Unlike JavaScript, it never silently coerces a number to a string or vice versa. This causes TypeError that surprises beginners from loosely typed languages.
age = 25 print(f"I am {age} years old") pi = 3.14159265 print(f"Pi to 2 dp: {pi:.2f}")
❌ Mistake 8 — Shallow copy vs deep copy confusion
Assigning a list to a new variable does not copy it — both names point to the same object. Only a deep copy is truly independent.
import copy original = [[1, 2], [3, 4]] alias = original alias[0][0] = 99 print("Original after alias mutation:", original) deep = copy.deepcopy(original) deep[0][0] = 0 print("Original after deep copy mutation:", original)
❌ Mistake 9 — Ignoring return values from string methods
Strings are immutable. Methods like .upper(), .strip(), and .replace() return a new string, they do not modify in place.
name = " glenn " name.strip() print(name) name = name.strip() print(name) text = "hello world" text = text.upper().replace("WORLD", "PYTHON") print(text)
❌ Mistake 10 — Writing loops when a built-in would do
Python ships with a rich set of built-in functions and comprehensions. Writing an explicit loop where a one-liner exists is slower and harder to read.
squares = [] for n in range(10): if n % 2 == 0: squares.append(n ** 2) print(squares) squares = [n**2 for n in range(10) if n % 2 == 0] print(squares) data = [1, 5, 3, 9, 2] print("Sum:", sum(data)) print("Max:", max(data)) print("Min:", min(data))
sum(), max(), min(), any(), all(), zip(), enumerate(), and sorted() exist because these patterns appear in almost every Python program. Learn them early.✅ Python Best Practices — Write Code Like a Pro
The habits that separate readable, maintainable Python from spaghetti code
Knowing the syntax is only half of Python. The other half is knowing the conventions, patterns, and habits that make your code easy to read, debug, and maintain — both for yourself six months from now and for anyone who works with your code. These practices are not opinions; they are the documented conventions of the Python community, codified in PEP 8 and reinforced by years of collective experience.
📏 Follow PEP 8 — Python's Official Style Guide
PEP 8 covers naming conventions, indentation, line length, blank lines, and import ordering. Most professional Python codebases enforce it automatically with tools like black or ruff.
user_name = "Glenn" def calculate_average(scores): ... class UserAccount: ... class DataProcessor: ... MAX_RETRIES = 3 BASE_URL = "https://api.example.com" _internal_helper = "not for external use"
📝 Write Docstrings for Every Public Function and Class
Docstrings are first-class documentation that tools like Sphinx, IDE tooltips, and help() can read. A function without a docstring forces the reader to study the implementation to understand intent.
def calculate_bmi(weight_kg: float, height_m: float) -> float: if height_m <= 0: raise ValueError("Height must be positive") return round(weight_kg / height_m ** 2, 2)
🔒 Use Type Hints — Standard Practice by 2026
Python's type hints don't change runtime behaviour, but they dramatically improve readability, enable IDE autocompletion, and allow static type checkers like mypy to catch bugs before your code runs.
from typing import Optional, TypedDict def find_user(user_id: int) -> Optional[dict]: ... def parse_value(raw: str) -> int | float: try: return int(raw) except ValueError: return float(raw) class Article(TypedDict): title: str author: str word_count: int published: bool
🧪 Write Tests — Even Simple Ones
Untested code is broken code waiting to be discovered. Python's built-in unittest and the popular third-party pytest make testing straightforward. Even a handful of tests catches regressions before they reach production.
The pytest example is omitted because Pyodide does not include pytest; run this code locally with pytest installed.
📦 Use dataclasses to Eliminate Boilerplate
The @dataclass decorator (Python 3.7+) auto-generates __init__, __repr__, and __eq__ from annotated fields, reducing repetitive code dramatically.
from dataclasses import dataclass, field from typing import List @dataclass class Student: name: str grade: int scores: List[float] = field(default_factory=list) @property def average(self) -> float: return sum(self.scores) / len(self.scores) if self.scores else 0.0 s = Student("Ana", 10) s.scores.extend([88, 92, 95]) print(s) print(f"Average: {s.average:.2f}")
🔧 Essential Python Built-in Functions You Must Know
Python ships with 70+ built-in functions — these appear in virtually every real program
One of Python's greatest strengths is how much useful functionality is available without importing anything. These built-in functions are always available, highly optimised in C, and cover the most common programming needs. Knowing them means less code, fewer bugs, and faster programs.
enumerate() — Loop with index
Returns an iterator of (index, value) pairs. Eliminates the need for a manual counter variable.
fruits = ["apple", "banana", "cherry"] for i, fruit in enumerate(fruits, start=1): print(f"{i}. {fruit}")
zip() — Combine iterables
Pairs up items from multiple iterables. Perfect for looping over two related lists simultaneously.
names = ["Ana", "Bob", "Cara"] scores = [88, 92, 79] for name, score in zip(names, scores): print(f"{name}: {score}")
sorted() & .sort()
sorted() returns a new sorted list. .sort() sorts in place. Both accept a key function.
words = ["banana", "Apple", "cherry"] print(sorted(words, key=str.lower)) people = [{"name":"Bob","age":30},{"name":"Ana","age":25}] people.sort(key=lambda p: p["age"]) print(people)
map() & filter()
Apply a function to every item (map), or keep only items matching a condition (filter). Both return lazy iterators.
nums = [1, 2, 3, 4, 5, 6] doubled = list(map(lambda x: x*2, nums)) evens = list(filter(lambda x: x%2==0, nums)) print(doubled) print(evens)
any() & all()
any() returns True if at least one item is truthy. all() returns True only if every item is truthy. Both short-circuit.
scores = [85, 92, 78, 95, 88] print(all(s >= 70 for s in scores)) print(any(s >= 90 for s in scores)) required = ["name", "email", "age"] data = {"name":"Ana", "email":"a@b.com", "age":25} print(all(k in data for k in required))
isinstance() & type()
isinstance() is the correct way to check an object's type — it handles inheritance. type() gives the exact type without inheritance awareness.
class Animal: pass class Dog(Animal): pass d = Dog() print(isinstance(d, Dog)) print(isinstance(d, Animal)) print(type(d) is Animal)
📂 File Handling & Error Handling in Python
Reading, writing, and processing files safely — skills every Python developer needs daily
Almost every real Python program interacts with files — reading configuration, processing CSVs, writing logs, parsing JSON. Doing this correctly means using context managers, handling exceptions gracefully, and choosing the right encoding.
📖 Reading and Writing Files Safely
Always use the with statement when opening files. It guarantees the file is closed even if an exception occurs — no resource leaks, no corrupted files.
The file I/O example is omitted because the browser environment lacks the actual files; run this code locally with existing files.
encoding="utf-8" explicitly. The default encoding varies by platform and can cause mysterious bugs when your code runs on a different machine.🗂️ pathlib — Modern File Path Handling
The pathlib module (Python 3.4+) provides an object-oriented interface to filesystem paths. It is cleaner, more readable, and cross-platform compared to old-style string manipulation with os.path.
from pathlib import Path home = Path.home() project = Path("myproject") data = project / "data" / "input.csv" print(f"Name: {data.name}") print(f"Stem: {data.stem}") print(f"Suffix: {data.suffix}") print(f"Parent: {data.parent}") print(f"Exists: {data.exists()}") for pyfile in Path(".").glob("*.py"): print(pyfile)
📋 Working with CSV and JSON
CSV and JSON are the two formats you will encounter most often in real-world Python work — spreadsheet exports, API responses, and configuration files.
import csv, json rows = [{"name":"Ana","score":95},{"name":"Bob","score":87}] with open("results.csv", "w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=["name","score"]) writer.writeheader() writer.writerows(rows) data = {"user": "Glenn", "level": "advanced", "scores": [95, 88, 92]} with open("user.json", "w") as f: json.dump(data, f, indent=2)
🏛️ Python OOP Deep Dive
Classes, dunder methods, properties, and dataclasses with full examples
🏗️ Defining Classes & __init__
A class bundles data (attributes) and behaviour (methods). __init__ runs on every new instance. self refers to the instance — not a keyword, just a strong convention.
class BankAccount: def __init__(self, owner: str, balance: float = 0.0): self.owner = owner self.balance = balance self._history = [] def deposit(self, amount: float): if amount <= 0: raise ValueError("Amount must be positive") self.balance += amount self._history.append(f"+{amount}") def withdraw(self, amount: float): if amount > self.balance: raise ValueError("Insufficient funds") self.balance -= amount self._history.append(f"-{amount}") def statement(self) -> str: return f"{self.owner}: ${self.balance:.2f}" acc = BankAccount("Alice", 500) acc.deposit(200) acc.withdraw(50) print(acc.statement())
self as its first argument. Python passes it automatically — you never supply it when calling acc.deposit(200).✨ Dunder Methods (Magic Methods)
Python calls dunder methods automatically in response to built-in operations. Implementing them makes your objects feel native — comparable with ==, printable, addable with +.
class Vector: def __init__(self, x, y): self.x, self.y = x, y def __repr__(self): return f"Vector({self.x}, {self.y})" def __add__(self, other): return Vector(self.x + other.x, self.y + other.y) def __eq__(self, other): return self.x == other.x and self.y == other.y def __len__(self): import math return int(math.hypot(self.x, self.y)) v1 = Vector(3, 4) v2 = Vector(1, 2) print(v1 + v2) print(v1 == v2) print("Length of v1:", len(v1))
🎯 @property, @classmethod, @staticmethod
@property turns a method into a computed attribute. @classmethod receives the class for alternative constructors. @staticmethod is a helper namespaced inside the class with no implicit argument.
class Temperature: def __init__(self, celsius: float): self._celsius = celsius @property def celsius(self): return self._celsius @celsius.setter def celsius(self, value): if value < -273.15: raise ValueError("Below absolute zero!") self._celsius = value @property def fahrenheit(self): return self._celsius * 9/5 + 32 @classmethod def from_fahrenheit(cls, f: float): return cls((f - 32) * 5/9) @staticmethod def is_freezing(celsius: float) -> bool: return celsius <= 0 t = Temperature.from_fahrenheit(212) print(f"Celsius: {t.celsius}") print(f"Fahrenheit: {t.fahrenheit}") print(f"Is freezing at -5°C? {Temperature.is_freezing(-5)}")
🧠 How Python Actually Runs Your Code
A look under the hood — what happens between typing python script.py and seeing output
Most tutorials teach syntax without explaining what the interpreter does with it. Understanding this layer helps explain why certain patterns are fast, why others are slow, and why some bugs happen at all.
📦 Bytecode and the CPython Interpreter
When you run a .py file, CPython (the reference implementation most people mean by "Python") first compiles your source into an intermediate form called bytecode — a set of low-level instructions stored in .pyc files. The interpreter then executes this bytecode one instruction at a time in a loop. You can inspect it yourself:
import dis def add(a, b): return a + b dis.dis(add)
dis.dis() on a function shows exactly which bytecode instructions Python generated. It's a useful way to see why, for example, tuple unpacking is fast — it compiles to a handful of direct instructions rather than a general-purpose loop.🔗 Names, Objects, and Reference Counting
A common early confusion is thinking that variables "contain" values the way boxes contain items. In Python, variables are names bound to objects. Assignment doesn't copy data — it points a name at an existing object in memory.
a = [1, 2, 3] b = a print("Same object?", a is b) print("id(a):", id(a)) print("id(b):", id(b)) b.append(4) print("a after b.append(4):", a)
Every object keeps a count of how many names point to it. When that count reaches zero, CPython frees the memory immediately — this is reference counting, and it's why Python generally doesn't need you to manage memory by hand. Circular references (an object that indirectly refers to itself) are handled separately by a periodic cyclic garbage collector.
⏱️ Why Some Operations Are Faster Than Others
Understanding the underlying data structures explains real performance differences you'll run into:
- ✓Lists are backed by contiguous arrays — appending at the end is fast (amortised O(1)), but inserting at the front is O(n) because every element has to shift.
- ✓Dictionaries and sets are hash tables — lookups are close to O(1) regardless of size, which is why
x in my_dictis far faster thanx in my_listfor large collections. - ✓Strings are immutable, so every concatenation with
+allocates a new string — this is why repeated concatenation in a loop is quadratic, while"".join(parts)is linear. - ✓Tuples are slightly cheaper than lists in both memory and creation time because they don't need to support resizing.
🧩 Common Algorithmic Patterns in Python
Recognisable shapes that show up across a huge range of problems
Beyond individual language features, a handful of algorithmic patterns come up again and again — in coding interviews, in data processing scripts, and in everyday problem solving. Recognising the pattern is often the hard part; the Python implementation is usually short once you see it.
👉 Two Pointers
Two indices move through a sequence — usually from opposite ends or at different speeds — to avoid a nested loop. Useful for searching sorted data, detecting palindromes, or removing duplicates in place.
def is_palindrome(s: str) -> bool: left, right = 0, len(s) - 1 while left < right: if s[left] != s[right]: return False left += 1 right -= 1 return True print(is_palindrome("racecar")) print(is_palindrome("python"))
🪟 Sliding Window
A window of fixed or variable size slides across a sequence, keeping a running total or count instead of recomputing from scratch each time. Common for "longest substring" or "maximum sum subarray" style problems.
def max_sum_subarray(nums: list[int], k: int) -> int: window_sum = sum(nums[:k]) best = window_sum for i in range(k, len(nums)): window_sum += nums[i] - nums[i - k] best = max(best, window_sum) return best print(max_sum_subarray([2, 1, 5, 1, 3, 2], 3))
🧮 Hash Map Counting
A dictionary used to count occurrences turns many "find duplicates" or "find the most frequent item" problems into a single linear pass instead of a nested loop.
from collections import Counter words = "the quick brown fox jumps over the lazy dog the fox runs".split() counts = Counter(words) print(counts.most_common(2))
collections.Counter is a dict subclass built exactly for this pattern — reach for it before writing a manual counting loop.🔁 Recursion & Memoisation
Some problems are naturally defined in terms of smaller versions of themselves. Recursion expresses that directly; memoisation avoids recomputing the same subproblem twice.
from functools import lru_cache @lru_cache(maxsize=None) def fibonacci(n: int) -> int: if n < 2: return n return fibonacci(n - 1) + fibonacci(n - 2) print([fibonacci(n) for n in range(10)])
@lru_cache, naive recursive Fibonacci recomputes the same values exponentially many times. The decorator turns it into a fast, linear-time solution with no change to the algorithm's structure.🏗️ 6 Interactive Real-World Python Projects
Click any project to explore the full walkthrough — each card includes a live demo snippet
Reading about a concept and building something with it are different skills. Each project below is a documented walkthrough with runnable code, step‑by‑step explanations, and a link to the complete guide. Click any card to open the detailed project page, or expand the demo to see the code in action right here.
Personal Finance Tracker Beginner
Weather CLI Tool Beginner
Job Listing Scraper Intermediate
Stock Price Dashboard Intermediate
AI-Powered Text Summariser Advanced
REST API with FastAPI & SQLite Advanced
Each project includes a complete walkthrough
Click any card to open the full guide with step‑by‑step instructions, code, and deployment notes.
🛠️ The Python Ecosystem — Tools Every Developer Uses
Beyond the language — the libraries, tools, and workflows professional Python developers rely on daily
Learning Python syntax is the beginning, not the end. Professional Python development involves a constellation of tools: package managers, virtual environments, formatters, linters, testing frameworks, and domain libraries. This guide maps the essential tools across every area of Python work.
📦 Package Management — pip, venv, and poetry
Every Python project should live in a virtual environment — an isolated directory that contains its own Python interpreter and packages, completely separate from your system Python.
python -m venv .venv source .venv/bin/activate .venv\Scripts\activate pip install requests pandas flask pip freeze > requirements.txt pip install -r requirements.txt poetry new my-project poetry add requests poetry run python main.py
🎨 Code Quality Tools — Black, Ruff, mypy
Modern Python development automates code style. These tools run as pre-commit hooks or in CI/CD pipelines to ensure every line of code that enters the codebase meets a consistent standard.
pip install black black . pip install ruff ruff check . ruff check --fix . pip install mypy mypy main.py mypy src/ pip install pre-commit pre-commit install
Web Frameworks
Flask — lightweight, ideal for APIs. Django — batteries-included for full applications. FastAPI — modern, async-first, automatic OpenAPI docs.
Explore →Data Science Stack
NumPy for arrays. Pandas for DataFrames. Matplotlib and Seaborn for visualisation. Jupyter for interactive exploration.
Explore →ML & AI Libraries
scikit-learn for classical ML. TensorFlow and PyTorch for deep learning. HuggingFace Transformers for NLP and LLMs.
Explore →Testing Frameworks
pytest — the standard. unittest — built-in, no install needed. hypothesis — property-based testing. pytest-cov for coverage.
Explore →Automation & CLI
Click and Typer for CLI apps. Schedule for task scheduling. Celery for distributed task queues. Playwright for browser automation.
Explore →Deployment & DevOps
Docker for containerisation. GitHub Actions for CI/CD. Render, Railway, Fly.io for free-tier hosting. AWS Lambda for serverless Python.
Explore →🎯 Python Interview Preparation — Questions & Answers
The most commonly asked Python interview questions at tech companies, with detailed answers
Python interviews range from basic syntax questions to tricky language internals, data structure problems, and system design. Preparation helps a lot here. Below are the questions that appear most frequently across junior, mid-level, and senior Python developer interviews.
A list is mutable — you can add, remove, or change elements after creation. A tuple is immutable — once created it cannot be changed. Tuples are faster to iterate and can be used as dictionary keys (because they are hashable), whereas lists cannot. Use tuples for data that should not change (coordinates, database records) and lists for collections that will be modified.
A generator is a function that uses yield instead of return. It returns a generator object — a lazy iterator that produces values one at a time on demand, rather than computing and storing all values at once. Use generators when working with large datasets that do not fit in memory, producing infinite sequences, or building data pipelines. A generator expression uses negligible memory; the equivalent list comprehension would allocate memory for all items immediately.
The Global Interpreter Lock (GIL) is a mutex in CPython that allows only one thread to execute Python bytecode at a time. Even on multi-core processors, Python threads cannot run truly in parallel for CPU-bound tasks. However, the GIL is released during I/O operations, so threading works well for I/O-bound workloads. For true CPU parallelism use multiprocessing or concurrent.futures.ProcessPoolExecutor. Python 3.13 introduced experimental no-GIL builds.
A @staticmethod receives no implicit first argument — it is essentially a regular function that lives inside a class for organisational purposes. A @classmethod receives the class itself as its first argument (cls). This makes it ideal for alternative constructors — factory methods that create instances in different ways. Use @staticmethod for utilities logically belonging to the class. Use @classmethod when the method needs to know about the class itself, especially for inheritance-aware construction.
Python uses reference counting as its primary memory management strategy. Every object tracks how many names point to it. When the count drops to zero, memory is immediately freed. However, reference counting cannot handle circular references. CPython's cyclic garbage collector runs periodically to detect and break these cycles. For performance-critical code, minimise circular references and use __slots__ to reduce per-object memory overhead.
A decorator is a callable that takes a function as input and returns a new function that wraps the original — extending or modifying its behaviour without changing its source code. @my_decorator above a function is syntactic sugar for func = my_decorator(func). Always use @functools.wraps(func) inside your wrapper to preserve the original function's metadata. Decorators power Flask routes, pytest fixtures, @lru_cache, and authentication everywhere in Python.
A list comprehension eagerly creates a full list in memory — use when you need to iterate multiple times or need random access. A generator expression is lazy — it produces values on demand — use for large datasets or single-pass pipelines. map() also returns a lazy iterator but requires a function object, making it slightly less readable. In modern Python, list comprehensions and generator expressions are preferred for clarity.
Dunder (double underscore) methods — also called magic methods — are how Python implements its data model. When you write a + b, Python calls a.__add__(b). When you write len(obj), Python calls obj.__len__(). By implementing these methods in your own classes, your objects integrate seamlessly with Python's built-in syntax. Key ones: __init__, __repr__, __str__, __eq__, __hash__, __len__, __iter__, __next__, __enter__, __exit__.
asyncio is Python's built-in library for writing concurrent code using the async/await syntax. It is event-loop based — instead of blocking while waiting for I/O, a coroutine yields control back to the event loop, which can run other tasks. Use async Python when your bottleneck is I/O: network requests, database queries, file reads. Do not use it for CPU-bound work — for that, use multiprocessing. The key rule: never call time.sleep() inside an async function — use await asyncio.sleep() instead.
A context manager is any object that implements __enter__ and __exit__. The with statement calls __enter__ on entry and __exit__ on exit — even if an exception is raised. This pattern guarantees cleanup: closing files, releasing locks, committing or rolling back database transactions. You can also create context managers with @contextlib.contextmanager — a generator function where code before yield is the setup and code after is the teardown.
📚 Curated Python Learning Resources
The books, documentation, and practice platforms that complement these lessons
Learning from a single source is never enough. The most effective way to learn Python combines structured lessons with official documentation, books, and hands-on problem solving. Below are resources that consistently hold up well at each stage of the learning journey.
Official Python Documentation
The most authoritative source for any Python question. The tutorial at docs.python.org/3/tutorial is surprisingly readable. The library reference covers every built-in function and standard library module in exhaustive detail.
Fluent Python (Luciano Ramalho)
A strong book for developers who already know the basics and want to understand Python deeply — data model, functions as objects, OOP idioms, control flow, metaprogramming. The 2nd edition covers Python 3.10+.
LeetCode & HackerRank
Practice data structures and algorithms in Python with immediate feedback. LeetCode's Python 3 tag filters to problems that test Python-specific knowledge. Even 30 minutes of problem-solving practice per day adds up.
Real Python (realpython.com)
High-quality tutorials and articles on nearly every Python topic — from beginner walkthroughs to deep dives on CPython internals. Particularly strong on web scraping, REST APIs, testing, async Python, and packaging.
Python Discord & Reddit r/learnpython
Two of the most active Python learning communities online. Post your code, ask questions, and get answers from experienced developers. Python Discord runs live code reviews and weekly challenges.
Exercism.io — Python Track
85+ Python exercises organised by concept, with human mentor review on every submission. Unlike LeetCode which focuses on algorithms, Exercism focuses on idiomatic, well-structured Python — the style and conventions that matter in a professional codebase.
How This Curriculum Is Built
A few design choices behind every module
Runnable Examples
Edit code, see results. No local setup needed — experiment directly in your browser.
Explained From First Principles
From variable scope to dunder methods, every concept is explained rather than assumed.
Project-First
Build a small portfolio while you learn: CLI tools, web scrapers, data visualisations.
Career-Relevant Context
Algorithms, interview patterns, and system design basics included throughout.
Organised for Reference
Indexed and cross-linked so you can find any Python topic quickly, not just read start to finish.
Kept Up to Date
Modules are revised as Python's standard library and typing system evolve.
Suggested Learning Paths
Structured sequences for different goals
Python Foundations
For absolute beginners. Learn the basics, write your first scripts, and build small projects.
- ✓Zero prerequisites required
- ✓40 hands-on exercises
- ✓5 portfolio projects
Data Handling
Work with real datasets, clean and analyse them, create visualisations. No math degree required.
- ✓NumPy & Pandas basics
- ✓Plotting with Matplotlib
- ✓Real-world datasets
Web & APIs
Build dynamic web apps with Flask or Django, connect to databases, and deploy your projects.
- ✓Flask & Django basics
- ✓Database integration
- ✓RESTful API design
Async & Advanced Python
Master modern Python: async/await, type hints, generators, context managers, and clean architecture.
- ✓asyncio & aiohttp
- ✓Type hints with mypy
- ✓Advanced decorators
Python vs. Other Languages
Understanding where Python fits and when to choose it
| Feature | Python 🐍 | JavaScript | Java | C++ |
|---|---|---|---|---|
| Learning Curve | Very Gentle ✓ | Moderate | Steep | Very Steep |
| Common Uses | AI/ML, Data, Scripts ✓ | Web Front-end | Enterprise Apps | Systems / Games |
| Readability | Excellent ✓ | Good | Verbose | Complex |
| Async Support | asyncio ✓ | Native (Promises) | Project Loom | Boost.Asio |
| AI/ML Ecosystem | Very large ✓ | Limited | Moderate | Limited |
| Raw Speed | Interpreted | Fast (JIT) | Fast (JVM) | Fastest ✓ |
Python's own interpreted speed is offset in practice by NumPy and PyTorch calling optimised C or C++ code under the hood.
A Suggested Study Roadmap
From your first script to comfortable, professional-level Python
Variables, data types, conditionals, loops, functions, file I/O, error handling. Goal: write your first working script that automates a real task.
Classes, inheritance, dunder methods, packages, virtual environments. New: generators, context managers, exception hierarchies.
asyncio, decorators, type hints, mypy. Write well-typed, concurrent Python that reads clearly.
Data Science
NumPy, Pandas, Scikit-learn
Web Dev
Flask, Django, FastAPI
Automation
Selenium, Playwright
AI / ML
TensorFlow, HuggingFace
📖 Python Key Concepts Glossary
Essential terms explained in plain language — click any card to jump to the related section
variableA named container holding a value. Python infers the type automatically — no declaration needed.
functionA reusable block defined with def. First-class objects — you can pass them around like variables.
generatorA function using yield to produce values lazily. Memory-efficient for large datasets and infinite sequences.
decoratorA function that wraps another to extend its behaviour without modifying it. Written with @.
async/awaitKeywords that mark non-blocking coroutines. await yields control back to the event loop while waiting for I/O.
context managerThe with statement pattern. Guarantees setup and teardown (open/close, acquire/release) even on errors.
ProtocolA structural type hint. Any class implementing the required methods satisfies the Protocol — no explicit inheritance needed.
dataclassA decorator that auto-generates __init__, __repr__, and __eq__ from annotated fields. Less boilerplate.
About the Author
Who wrote and maintains this curriculum
Glenn Junsay Pansensoy
Python Developer, Writer & Teacher — Diplahan, Zamboanga Sibugay, Philippines
I picked up Python while automating small tasks and kept going from there. My approach to writing this curriculum: skip the jargon, write real code, and always explain the why behind a concept, not just the syntax.
- Independent Python developer and technical writer
- Self-taught, with a focus on data science topics
- Maintains this curriculum and Poetic Codes, a long-form essay publication
🐞 Debugging Python Code Effectively
Reading errors, using the built-in debugger, and forming a repeatable process for finding bugs
Debugging is a skill on its own, separate from knowing the language. Most of the time spent "learning to code" is really spent learning to figure out why code doesn't do what you expected. This section covers the tools and habits that make that process faster.
📜 Reading a Traceback Top to Bottom (Then Bottom to Top)
A traceback lists the chain of function calls that led to an error, in the order they happened. Beginners often read it top-down like normal text, but the most useful information — the actual error type and message — is at the very bottom. Read the last line first, then walk upward to see which call led there.
def get_first_item(items): return items[0] def process(data): return get_first_item(data) process([])
IndexError: list index out of range with a traceback showing process() called get_first_item(), which failed on the indexing line. The bottom line tells you what went wrong; the frames above it tell you where in your call chain to look.🔎 Recognising Common Exception Types on Sight
Learning to recognise an exception type at a glance saves time — each one narrows down the search significantly before you even look at the message.
- ✓
NameError— a name is used before it's defined, usually a typo or a variable used outside its scope - ✓
TypeError— an operation was applied to a type it doesn't support, like adding a string to an integer - ✓
ValueError— the type is correct but the value isn't acceptable, likeint("abc") - ✓
KeyError— a dictionary lookup used a key that doesn't exist - ✓
IndexError— a sequence was accessed with an index outside its valid range - ✓
AttributeError— code tried to use a method or attribute an object doesn't have, often from a typo or aNonewhere an object was expected
🖨️ Print Debugging — Simple, and Still Worth Doing Well
Sprinkling print() statements is often dismissed as unsophisticated, but done deliberately it's a fast way to confirm assumptions. The key is printing enough context to be useful, not just a bare value.
def calculate_discount(price, percent): print(f"[calculate_discount] price={price!r} percent={percent!r}") discount = price * (percent / 100) print(f"[calculate_discount] discount={discount!r}") return price - discount print(calculate_discount(100, 15))
!r in an f-string shows the repr() of a value, which distinguishes "15" (a string) from 15 (an integer) — a common source of confusing bugs that a plain print can hide.🛑 The Built-in Debugger — breakpoint()
Python 3.7+ includes a built-in breakpoint() function that drops you into an interactive debugger (pdb) at that exact line. It's more powerful than print debugging because you can inspect and change variables live, step through code line by line, and explore the call stack.
def total_price(items): total = 0 for item in items: breakpoint() # execution pauses here total += item["price"] * item["qty"] return total
Once paused, a few commands cover most needs: n (next line), s (step into a function call), c (continue running), p variable_name (print a variable), and q (quit the debugger).
breakpoint() requires an interactive terminal, so it won't pause execution inside the browser runner on this page — try it locally to see it in action.🧭 A Repeatable Process for Chasing Down a Bug
Random trial and error tends to take longer than a deliberate process. A reasonably reliable sequence:
- Reproduce the bug consistently before trying to fix anything — an intermittent bug you can't reliably trigger is much harder to confirm as fixed.
- Read the full traceback, bottom to top, and identify the exact line that raised the exception.
- Form a specific hypothesis about what the value of a variable is at that point, rather than a vague sense that "something's wrong."
- Check that hypothesis directly — with a print statement,
breakpoint(), or by running just that line in isolation. - Fix the actual cause, not just the symptom — a
try/exceptthat silently swallows the error usually just delays the same bug. - Add a small test or assertion that would have caught this case, so it doesn't come back unnoticed.
🧪 Writing Your First Unit Tests
Using Python's built-in unittest module to catch bugs before they reach production
A test is just code that checks other code. You don't need a large framework to start — Python's standard library includes unittest, which is enough to build the habit before reaching for pytest later.
✅ A Minimal Test Case
A test case is a class that inherits from unittest.TestCase. Each method starting with test_ is run independently, and assertEqual, assertTrue, and similar methods report a clear failure message when something doesn't match.
import unittest def apply_discount(price: float, percent: float) -> float: if not 0 <= percent <= 100: raise ValueError("percent must be between 0 and 100") return round(price * (1 - percent / 100), 2) class TestApplyDiscount(unittest.TestCase): def test_typical_discount(self): self.assertEqual(apply_discount(100, 20), 80.0) def test_zero_percent_returns_original_price(self): self.assertEqual(apply_discount(50, 0), 50.0) def test_invalid_percent_raises(self): with self.assertRaises(ValueError): apply_discount(100, 150) runner = unittest.TextTestRunner(verbosity=2) suite = unittest.TestLoader().loadTestsFromTestCase(TestApplyDiscount) runner.run(suite)
unittest is part of the standard library, so it's available without installing anything, unlike pytest.🎯 What Makes a Good Test
- ✓Test one behaviour per method — a failing test name should tell you roughly what broke without reading the assertion
- ✓Cover the typical case, an edge case (empty input, zero, boundary values), and an error case
- ✓Keep tests independent — one test's outcome should never depend on another test running first
- ✓Prefer
assertRaisesover manually catching an exception and checking a flag - ✓Name test methods descriptively —
test_negative_percent_raisesis more useful thantest_2
🏗️ setUp and tearDown
When several tests need the same starting state, setUp() runs before every test method and tearDown() runs after — useful for resetting objects, closing files, or cleaning up temporary state.
import unittest class ShoppingCart: def __init__(self): self.items = [] def add(self, name, price): self.items.append((name, price)) def total(self): return sum(price for _, price in self.items) class TestShoppingCart(unittest.TestCase): def setUp(self): self.cart = ShoppingCart() def test_empty_cart_total_is_zero(self): self.assertEqual(self.cart.total(), 0) def test_total_sums_item_prices(self): self.cart.add("Book", 12.5) self.cart.add("Pen", 1.5) self.assertEqual(self.cart.total(), 14.0) runner = unittest.TextTestRunner(verbosity=2) suite = unittest.TestLoader().loadTestsFromTestCase(TestShoppingCart) runner.run(suite)
setUp() runs fresh before each test, test_empty_cart_total_is_zero and test_total_sums_item_prices each get their own brand-new ShoppingCart — one test can't accidentally leave state that affects the other.Frequently Asked Questions
Straightforward answers about the curriculum
No. The beginner path starts from absolute zero. We explain every concept — including what a variable is, why indentation matters, and how the computer actually runs your code.
Yes. Every lesson, code example, pathway, and community feature is free to use. There are no premium tiers or paywalled content — the site is supported by ads.
Seven new modules: Python Sets, Modules & Packages, Exception Handling (in depth), Iterators & Generators, Async & Concurrency (asyncio), Decorators In Depth, and Type Hints & mypy. They cover the gaps between beginner basics and more advanced, production-style Python.
Most learners complete the foundations path in about 8–10 weeks, studying 5–7 hours per week. Reaching a comfortable, job-relevant level generally takes several more months of consistent practice beyond that, including the advanced modules.
Nothing to get started. Every code example runs in your browser. When you're ready to code locally, there's a short guide to installing Python 3 and VS Code — about 10 minutes total.
All content targets Python 3.10+, with a focus on 3.12+ features including improved error messages, performance improvements, and modern typing syntax. We do not cover Python 2, which reached end-of-life in 2020.
Start with the fundamentals and OOP first. async is an intermediate-to-advanced topic best learned once you're comfortable with functions, classes, and exception handling. Our async module is placed accordingly in the curriculum.
Roles commonly using Python include Data Scientist, ML Engineer, Backend Developer, Data Analyst, DevOps Engineer, and QA Automation Engineer. This content is designed with those real-world uses in mind, including type hints and async patterns that show up often in production code.
📬 Python, weekly
Short lessons, real code, and ecosystem news — sent about once a week
📦 Python Standard Library — The Hidden Treasure
Python ships with a vast standard library. These modules solve real problems without any pip install
Python's motto "batteries included" refers to its large standard library. Before reaching for a third-party package, it's worth checking whether the standard library already solves your problem. Below are the standard library modules you will encounter regularly in real-world Python development.
datetime — Dates and Times
Parse, format, and perform arithmetic on dates and times. The most important module for any application dealing with scheduling, logging, or user-facing time values.
collections — Specialised Containers
Counter, defaultdict, OrderedDict, deque, and namedtuple — supercharged versions of Python's built-in data structures.
itertools — Iterator Building Blocks
Lazy combinators for efficient looping. chain, cycle, islice, groupby, product, combinations, permutations — used constantly in data processing.
functools — Higher-Order Functions
lru_cache for memoisation, reduce for fold operations, partial for partial application, and wraps for writing correct decorators.
re — Regular Expressions
Pattern matching, extraction, and substitution with regular expressions. Essential for text processing, form validation, log parsing, and web scraping.
threading & multiprocessing
Use threading for I/O-bound concurrency (the GIL is released during I/O). Use multiprocessing for CPU-bound parallelism — each process gets its own Python interpreter and GIL.
argparse — Command-Line Interfaces
Build professional CLI tools with typed arguments, help text, subcommands, and default values — all from the standard library with no third-party dependency.
logging — Production-Grade Logging
Avoid relying on print() for debugging in production code. The logging module provides levels (DEBUG, INFO, WARNING, ERROR, CRITICAL), file handlers, formatters, and structured output.
🌍 Python Across Industries — Real-World Applications
How Python is used in different fields — and what you need to learn for each one
Python's versatility means the fundamentals you learn in this course apply directly to medicine, finance, journalism, education, and beyond. Understanding which libraries and patterns matter in your target field helps you focus your learning on what those roles actually need day to day.
Healthcare & Bioinformatics
Python is used for genomics analysis, medical imaging with SimpleITK, clinical trial data processing, and drug discovery pipelines. Libraries like BioPython handle DNA/protein sequences. Machine learning models built with scikit-learn and PyTorch support diagnostic imaging research.
Finance & Quantitative Trading
Hedge funds, investment banks, and fintech companies use Python for algorithmic trading, risk modelling, options pricing, and portfolio optimisation. pandas handles time-series data. NumPy powers vectorised mathematical operations. Libraries like QuantLib handle derivatives pricing.
Journalism & Data Journalism
Newsrooms use Python to work with public records, analyse election data, build interactive visualisations, and process structured datasets for reporting. BeautifulSoup, pandas, and Altair are common tools for data journalists.
Education & EdTech
Python is widely taught in universities and coding bootcamps. EdTech platforms often build backends with Django or FastAPI. Adaptive learning systems use ML models to personalise content, and Jupyter Notebooks are widely used for interactive teaching.
Manufacturing & IoT
Python runs on Raspberry Pi and embedded systems for sensor data collection, predictive maintenance, and quality control. MQTT libraries handle IoT messaging. OpenCV powers computer vision for automated defect detection on production lines.
Scientific Research
Python is a common choice in scientific computing today. The SciPy ecosystem — NumPy, SciPy, Matplotlib, and Jupyter — is used in physics simulations, climate modelling, astronomy, and materials science across many research institutions.
Choosing a Direction
The Python fundamentals covered here apply to all of these fields. Once you complete the foundations path, picking one specialisation and going deep — learning the domain-specific libraries, reading a bit of the field's literature, and building a couple of small projects — tends to be more useful than trying to cover everything at once.
💡 Python Reads Like Pseudocode — Here's Why That Matters
Understanding Python's design philosophy and why readable code is worth caring about
Guido van Rossum designed Python around one overriding principle: code is read far more often than it is written. Python's syntax leans on meaningful indentation, English-like keywords, and a preference for clarity over cleverness. Compare the same algorithm across languages:
🔍 Same Algorithm, Different Languages
Finding all prime numbers up to N using the Sieve of Eratosthenes:
def sieve_of_eratosthenes(limit: int) -> list[int]: is_prime = [True] * (limit + 1) is_prime[0] = is_prime[1] = False for i in range(2, int(limit**0.5) + 1): if is_prime[i]: for multiple in range(i*i, limit + 1, i): is_prime[multiple] = False return [n for n, prime in enumerate(is_prime) if prime] primes = sieve_of_eratosthenes(50) print("Primes up to 50:", primes)
is_prime, multiple), the loop structure, and the list comprehension at the end all read like a description of the algorithm itself. This is the Python ideal: code that documents itself.🐍 The Zen of Python — 19 Guiding Principles
Running import this in any Python interpreter prints the Zen of Python — 19 aphorisms that guide Python's design. The most relevant ones for everyday development emphasise clarity, simplicity, and explicitness over implicit magic.
import this✍️ Pythonic Code vs. Non-Pythonic Code
Writing "Pythonic" code means leaning on Python's own idioms rather than translating patterns from another language line by line. The difference shows up in both readability and, sometimes, performance.
items = [1, 2, 3, 4, 5, 6] i = 0 result = [] while i < len(items): if items[i] % 2 == 0: result.append(items[i] * 2) i += 1 result = [item * 2 for item in items if item % 2 == 0] a, b = 10, 20 temp = a a = b b = temp a, b = b, a my_dict = {"key": "value"} if "key" in my_dict: value = my_dict["key"] else: value = "default" value = my_dict.get("key", "default")
📋 Python Quick-Reference Cheat Sheet
The most important Python syntax patterns at a glance — bookmark this page
int, float, str, bool, and None. Operators cover arithmetic (+, -, *, /, //, %, **), comparison, logical, and assignment. The walrus operator := (Python 3.8+) assigns and returns a value in one expression. f-strings (Python 3.6+) are the recommended way to format strings.
x: int = 42 y: float = 3.14 s: str = "Hello, Python!" b: bool = True n = None print(10 // 3) print(10 % 3) print(2 ** 8) n = 0 while (n := n + 1) < 5: print(f"n is {n}") name, score = "Glenn", 98.5 print(f"{name}: {score:.1f}%") print(f"{1_000_000:,}") print(f"{'hello':>10}")
lst = [1, 2, 3, 4, 5] lst.append(6) lst.insert(0, 0) lst.pop() lst.sort(reverse=True) print(lst[1:4]) print(lst[::-1]) d = {"a": 1, "b": 2} d["c"] = 3 print(d.get("z", 0)) d = d | {"d": 4} print({k: v for k, v in d.items() if v > 1}) s = {1, 2, 3} s.add(4) s.discard(1) print({1,2,3} & {2,3,4}) print({1,2,3} | {3,4,5}) print({1,2,3} - {2,3}) t = (1, 2, 3) a, b, c = t first, *rest = t print(first, rest)
if/elif/else for branching, for and while for loops. Python 3.10+ adds match (pattern matching) for expressive branching. The else clause on loops runs only if the loop completed without a break.
x = 42 result = "positive" if x > 0 else "non-positive" match x: case 0: print("zero") case n if n > 0: print(f"positive: {n}") case _: print("negative") for i, val in enumerate(["a","b","c"], start=1): print(i, val) for x, y in zip([1,2,3], [4,5,6]): print(x + y) target = 7 for n in range(2, target): if target % n == 0: print("not prime") break else: print("prime")
def. Python supports positional-only (/), positional-or-keyword, keyword-only (*), default values, variable-length arguments (*args, **kwargs). lambda creates anonymous functions. Use functools.partial for partial application.
def demo(pos_only, /, normal, *, kw_only, default=10): print(pos_only, normal, kw_only, default) def flexible(*args, **kwargs): for a in args: print(a) for k, v in kwargs.items(): print(f"{k}={v}") square = lambda x: x**2 evens = list(filter(lambda x: x%2==0, range(10))) doubled = list(map(lambda x: x*2, range(5))) args = [1, 2, 3] kwargs = {"sep": ", ", "end": "!\n"} print(*args, **kwargs)
import this. Core aphorisms include "Beautiful is better than ugly", "Explicit is better than implicit", "Simple is better than complex", and "Readability counts".
import thisdict.get() for safe lookups. Non-Pythonic code often imitates patterns from other languages and is harder to read.
items = [1, 2, 3, 4, 5, 6] i = 0 result = [] while i < len(items): if items[i] % 2 == 0: result.append(items[i] * 2) i += 1 result = [item * 2 for item in items if item % 2 == 0] a, b = 10, 20 temp = a a = b b = temp a, b = b, a my_dict = {"key": "value"} if "key" in my_dict: value = my_dict["key"] else: value = "default" value = my_dict.get("key", "default")
🕰️ A Brief History of Python
How Python went from a Christmas project to one of the world's most widely used programming languages
Understanding where Python came from helps explain why it's designed the way it is — and why its community formed the way it did. Python's history is a story of patient, deliberate development and good timing.
📅 Python's Timeline
print("1989 — Guido van Rossum begins writing Python over Christmas") print("1991 — Python 0.9.0 released publicly on Usenet") print("1994 — Python 1.0 released") print("2000 — Python 2.0 released") print("2008 — Python 3.0 released") print("2020 — Python 2 officially reached end-of-life") print("2021 — Python 3.10: match/case, better errors") print("2022 — Python 3.11: significant interpreter speedups") print("2023 — Python 3.12: f-string improvements") print("2024 — Python 3.13: experimental free-threaded mode")
Why Python Spread So Widely
Python succeeded where some other "easy" languages didn't scale as well, because it never traded away power for simplicity. A beginner's first script and a large production system can look structurally similar. The language scales with your skill level in a way not every language manages.
The PSF & Community
The Python Software Foundation (PSF) is a non-profit that holds the intellectual property rights to Python. It funds PyCon conferences, grants to educators, and infrastructure for PyPI. The community's culture around inclusivity and mentorship is a deliberate, ongoing effort, not an accident.
Where Python Is Headed
With the JIT compiler work started in 3.13 and the experimental no-GIL mode, Python is actively addressing its traditional weakness around raw speed. Later releases aim to make Python more competitive with compiled languages for CPU-bound workloads, without giving up readability.
🏁 You've Built a Solid Foundation
Working through this curriculum — even partway — is a genuinely useful step, and one worth recognising.
✅ What This Curriculum Covers
- The fundamentals: variables, data types, control flow, functions
- Python's data structures: lists, dicts, sets, tuples
- Object‑oriented programming: classes, inheritance, polymorphism, encapsulation
- Advanced topics: generators, decorators, async/await, type hints
- Real‑world skills: file I/O, web scraping, data analysis, LLM APIs
- A small portfolio of projects that demonstrate real problem‑solving
- The ecosystem: pip, venv, pytest, black, mypy, and more
📚 Treat It as a Reference, Too
This curriculum is meant to be a living resource, not just a one-time read. Bookmark it, revisit sections as needed, and treat the code examples as something to experiment with, not just read — modifying and breaking things is often how concepts actually stick.
🚀 Where This Can Lead
The fundamentals here are the entry point to several directions:
- Data Analysis – Process and analyse datasets with Pandas and NumPy
- Machine Learning – Build predictive models with scikit‑learn and TensorFlow
- Web Development – Create APIs and full‑stack applications with Flask, Django, FastAPI
- Automation – Write scripts that save real time on manual work
- Scientific Computing – Simulate, model, and visualise complex systems
- AI Engineering – Work with LLMs, NLP, and computer vision
💌 A Note From the Author
This curriculum was put together by Glenn Junsay Pansensoy. The goal, as much as possible, has been to make it:
- Comprehensive — covering the fundamentals thoroughly
- Interactive — learning by doing, not just reading
- Practical — tied to real applications, not abstract exercises
- Free — accessible to anyone, anywhere, without a paywall
If any part of this helped, sharing it with someone else who's learning is genuinely appreciated.
🌟 A Few Reminders
- Use the right tool for the job — not every problem needs the most advanced feature
- Know your tools — the standard library solves more than it gets credit for
- Test your assumptions — code isn't correct until it's been checked
- Understand the theory behind the code, not just the syntax
- Consistency beats intensity — regular, smaller sessions tend to work better than occasional long ones
🙏 Thanks for Reading This Far
Thank you for spending time with this material. The skills covered here tend to transfer well — across other languages and across whatever domain you end up working in.
Poetic Bytes · 2026 · Written and maintained by Glenn Junsay Pansensoy
🗣️ Learner Voices
No comments yet — your voice starts the conversation.