Python logoPythonv3.14INTERMEDIATE

Python

Essential Python reference covering syntax, data structures, functions, OOP, comprehensions, type hints, virtual environments, and the standard library.

18 min read
pythonprogrammingbasicssyntaxdata-typescontrol-flowfunctionsoop
Loading your progress

Setup & Basics

Install Python, run scripts, and understand basic syntax.

Install Python and run scripts from the command line.

bash
# Install (macOS)
brew install python

# Install (Ubuntu/Debian)
sudo apt install python3 python3-pip

# Install (Windows) — download from https://python.org/downloads

# Check version
python3 --version

# Run a script
python3 app.py

# Interactive REPL
python3
💡 Use python3 explicitly — "python" may point to Python 2 on some systems
⚡ The REPL is great for testing snippets — type exit() or Ctrl+D to quit
📌 On Windows, use "py" instead of "python3" as the launcher
🟢 Use "python3 -m" to run installed modules as scripts (e.g., http.server, venv)
installsetupcli

Python variables are dynamically typed — no declaration keyword needed.

python
# Variables (no declaration keyword)
name = "Alice"
age = 30
price = 19.99
is_active = True

# Check type
type(name)    # <class 'str'>

# Multiple assignment
x, y, z = 1, 2, 3
a = b = c = 0
💡 Python is dynamically typed — variables can change type at any time
⚡ Use x, y = y, x to swap values — no temp variable needed
📌 Falsy values: None, 0, 0.0, empty string, empty list/dict/set, False
🟢 Use isinstance() over type() for type checking — it supports inheritance
variablestypesbasics

Strings & Formatting

String operations, methods, and formatting with f-strings.

Common string methods and operations.

python
s = "Hello, World!"
s.lower()          # "hello, world!"
s.upper()          # "HELLO, WORLD!"
s.strip()          # Remove whitespace
s.split(", ")      # ["Hello", "World!"]
s.replace("World", "Python")
s.startswith("Hello")  # True
"World" in s           # True
💡 Strings are immutable — methods return new strings, they don't modify in place
⚡ Use "in" for substring checks — it's more Pythonic than .find()
📌 .find() returns -1 if not found; .index() raises ValueError
🟢 Use s[::-1] to reverse a string with slicing
stringsmethods

Format strings with f-strings, .format(), and % operator.

python
name = "Alice"
age = 30

# f-strings (Python 3.6+ — preferred)
f"Name: {name}, Age: {age}"
f"Next year: {age + 1}"
f"Price: {19.99:.2f}"

# Debug syntax (3.8+) — prints variable name and value
f"{name=}, {age=}"   # "name='Alice', age=30"

# .format()
"Name: {}, Age: {}".format(name, age)
💡 f-strings are the fastest and most readable — use them by default
⚡ The debug f"{x=}" syntax (3.8+) is gold for quick print debugging
📌 Format specs: :.2f (2 decimals), :, (thousands), :>10 (right-align)
🟢 Use r"..." raw strings for regex patterns and Windows file paths
stringsformattingf-strings

Control Flow

Conditionals, loops, match statements, and flow control keywords.

Conditionals

if/elif/else statements and ternary expressions.

python
# if / elif / else
if score >= 90:
    grade = "A"
elif score >= 80:
    grade = "B"
else:
    grade = "C"

# Ternary (inline if)
status = "adult" if age >= 18 else "minor"
💡 Python uses and/or/not instead of &&/||/!
⚡ Chained comparisons like 0 < x < 100 are valid and Pythonic
📌 Check empty collections with "if items:" — no need for len(items) > 0
🟢 The ternary syntax is: value_if_true if condition else value_if_false
conditionalsifternary

Loops

for and while loops with break, continue, and else clauses.

python
# for loop
for item in items:
    print(item)

# range
for i in range(5):       # 0, 1, 2, 3, 4
    print(i)

# while loop
while count > 0:
    count -= 1

# break / continue
for x in range(10):
    if x == 3: continue  # skip 3
    if x == 7: break     # stop at 7
💡 Use enumerate() instead of range(len()) — it's cleaner and more Pythonic
⚡ zip() pairs elements from multiple iterables — stops at the shortest
📌 The else clause on a loop runs only if the loop did NOT hit a break
🟢 Use continue to skip an iteration, break to exit the loop entirely
loopsforwhilebreakcontinue

Structural pattern matching (Python 3.10+).

python
# Basic match
match command:
    case "quit":
        exit()
    case "hello":
        print("Hi!")
    case _:
        print("Unknown")
💡 match/case is structural pattern matching, not just a switch statement
⚡ Use | to match multiple values in one case (e.g., 500 | 502 | 503)
📌 The _ wildcard matches anything — use it as a default/fallback case
🟢 Add a guard with "if" after the pattern for conditional matching
matchpattern-matchingcontrol-flow

Data Structures

Lists, tuples, dictionaries, and sets — with comprehensions.

Lists

Ordered, mutable sequences with powerful methods.

python
nums = [1, 2, 3, 4, 5]
nums.append(6)          # Add to end
nums.insert(0, 0)       # Insert at index
nums.pop()              # Remove & return last
nums.remove(3)          # Remove first occurrence
nums.sort()             # Sort in place
len(nums)               # Length
nums[1:3]               # Slice [2, 3]
💡 .sort() modifies in place and returns None — use sorted() for a new list
⚡ Use first, *rest = list to unpack the head and tail
📌 .remove() deletes by value; del and .pop() delete by index
🟢 Slicing never raises IndexError — out-of-range slices return empty lists
listsdata-structures

Dictionaries

Key-value mappings with fast lookups.

python
d = {"name": "Alice", "age": 30}
d["name"]              # "Alice"
d.get("email", "N/A")  # "N/A" (default)
d["email"] = "a@b.com" # Add/update
d.keys()               # dict_keys
d.values()             # dict_values
d.items()              # key-value pairs
del d["age"]           # Remove key
💡 Use .get(key, default) to avoid KeyError on missing keys
⚡ The | merge operator (3.9+) creates a new dict — |= merges in place
📌 Use .items() to iterate over key-value pairs, not just keys
🟢 .setdefault() gets a value or inserts a default — great for counting/grouping
dictionariesdata-structures

Tuples & Sets

Immutable sequences and unique unordered collections.

python
# Tuples — immutable, ordered
coords = (10, 20)
x, y = coords          # Unpack
single = (42,)          # Single-element tuple needs comma

# Sets — unique, unordered
s = {1, 2, 3}
s.add(4)
s.discard(2)
a & b   # intersection
a | b   # union
a - b   # difference
💡 Use tuples for fixed collections — they're faster and hashable (can be dict keys)
⚡ Create an empty set with set(), not {} — braces create an empty dict
📌 .discard() is silent on missing elements; .remove() raises KeyError
🟢 Sets are perfect for deduplication: list(set(items)) removes duplicates
tuplessetsdata-structures

Comprehensions

Concise syntax for creating lists, dicts, and sets.

python
# List comprehension
squares = [x**2 for x in range(10)]
evens = [x for x in nums if x % 2 == 0]

# Dict comprehension
{k: v for k, v in pairs}

# Set comprehension
{x.lower() for x in words}
💡 List comps are faster than equivalent for loops — use them for simple transforms
⚡ Generator expressions use () instead of [] — they're lazy and memory-efficient
📌 Read nested comps left-to-right: "for row in matrix for n in row"
🟢 The walrus operator := lets you assign and filter in one expression
comprehensionslist-compgenerators

Functions

Define functions with default args, *args, **kwargs, and lambda expressions.

Function Basics

Define functions with parameters, defaults, and return values.

python
def greet(name, greeting="Hello"):
    """Return a greeting string."""
    return f"{greeting}, {name}!"

result = greet("Alice")
result = greet("Bob", greeting="Hi")
💡 Use / to mark positional-only params, * to mark keyword-only params
⚡ Functions can return multiple values as a tuple — unpack with a, b = func()
📌 Default arguments are evaluated once — never use mutable defaults like def f(x=[])
🟢 Add docstrings as the first line in a function for documentation
functionsparametersreturn

Accept variable arguments and unpack iterables into function calls.

python
# *args — variable positional arguments
def total(*nums):
    return sum(nums)

total(1, 2, 3)  # 6

# **kwargs — variable keyword arguments
def config(**options):
    for k, v in options.items():
        print(f"{k}: {v}")

config(host="localhost", port=5432)
💡 *args becomes a tuple, **kwargs becomes a dict inside the function
⚡ Use *list and **dict to unpack when calling functions
📌 Parameter order: positional, *args, keyword defaults, **kwargs
🟢 The *args/**kwargs pattern is essential for decorators and wrapper functions
argskwargsunpacking

Anonymous functions and built-in functions that take functions as arguments.

python
# Lambda — anonymous inline function
square = lambda x: x ** 2
add = lambda a, b: a + b

# map — apply function to each item
list(map(str.upper, ["a", "b"]))  # ["A", "B"]

# filter — keep items where function returns True
list(filter(lambda x: x > 0, [-1, 0, 1, 2]))

# sorted with key
sorted(users, key=lambda u: u["age"])
💡 Prefer list comprehensions over map/filter for readability
⚡ any() and all() short-circuit — great for validation checks
📌 sorted() always returns a new list; .sort() modifies in place
🟢 filter(None, items) removes all falsy values (None, 0, "", [])
lambdamapfilterhigher-order

Type Hints

Add type annotations for better code clarity and tooling support.

Annotate function parameters, return types, and variables.

python
# Function annotations
def greet(name: str, age: int = 0) -> str:
    return f"Hello {name}, age {age}"

# Variable annotations
count: int = 0
names: list[str] = ["Alice", "Bob"]
config: dict[str, int] = {"port": 8080}
💡 Type hints are not enforced at runtime — they're for documentation and tooling
⚡ Use int | str (3.10+) instead of Union[int, str] — it's cleaner
📌 Use list[str] directly (3.9+) instead of importing List from typing
🟢 Run mypy or pyright to check types statically: mypy app.py
typestype-hintsannotations

Classes & OOP

Classes, inheritance, dataclasses, and special methods.

Class Basics

Define classes with __init__, instance methods, and class/static methods.

python
class Dog:
    species = "Canine"  # Class variable

    def __init__(self, name, age):
        self.name = name    # Instance variable
        self.age = age

    def bark(self):
        return f"{self.name} says woof!"

rex = Dog("Rex", 5)
rex.bark()  # "Rex says woof!"
💡 Use @property to create computed attributes accessed without parentheses
⚡ @classmethod receives the class (cls) — great for alternative constructors
📌 @staticmethod receives neither self nor cls — it's just a namespaced function
🟢 Always define __repr__ for debugging — __str__ is for user-facing output
classesoopmethods

Extend classes with inheritance and use dataclasses for data containers.

python
# Inheritance
class Animal:
    def __init__(self, name):
        self.name = name
    def speak(self):
        raise NotImplementedError

class Cat(Animal):
    def speak(self):
        return f"{self.name} says meow!"

# Dataclass (3.7+)
from dataclasses import dataclass

@dataclass
class Point:
    x: float
    y: float
💡 Use super().__init__() to call the parent class constructor
⚡ Dataclasses auto-generate __init__, __repr__, and __eq__ from type annotations
📌 Use field(default_factory=list) for mutable defaults in dataclasses
🟢 @dataclass(frozen=True) makes instances immutable and hashable
inheritancedataclassesoop

Enums

Named constants grouped under a single type.

python
from enum import Enum, auto

class Color(Enum):
    RED = 1
    GREEN = 2
    BLUE = 3

Color.RED            # <Color.RED: 1>
Color.RED.name       # "RED"
Color.RED.value      # 1
Color(1)             # <Color.RED: 1> (lookup by value)
Color["RED"]         # <Color.RED: 1> (lookup by name)

# auto() — assign values automatically
class Status(Enum):
    PENDING = auto()
    ACTIVE = auto()
    DONE = auto()
💡 Use Enum to give names to a small fixed set of values
⚡ IntEnum/StrEnum members ARE ints/strs — compare them directly with literals
📌 auto() saves you from manually assigning incrementing values
🟢 Flag enums combine with | and check membership with in

Error Handling

Handle exceptions with try/except, raise errors, and define custom exceptions.

Catch and handle exceptions gracefully.

python
try:
    result = 10 / 0
except ZeroDivisionError:
    print("Cannot divide by zero")
except (TypeError, ValueError) as e:
    print(f"Error: {e}")
else:
    print("Success")        # Only runs if no exception
finally:
    print("Always runs")    # Cleanup
💡 Use specific exception types — bare "except:" catches everything including KeyboardInterrupt
⚡ The else block runs only when no exception occurs — great for success logic
📌 Use "raise" without arguments to re-raise the current exception
🟢 Custom exceptions should inherit from Exception, not BaseException
exceptionstryexcepterror-handling

File I/O

Read and write files, work with JSON, and use context managers.

Read, write, and append files. Parse and write JSON data.

python
# Read a file
with open("data.txt") as f:
    content = f.read()

# Write a file
with open("output.txt", "w") as f:
    f.write("Hello!")

# JSON
import json
data = json.loads('{"name": "Alice"}')
json_str = json.dumps(data, indent=2)
💡 Always use "with open(...)" — it automatically closes the file
⚡ json.load/dump work with files; json.loads/dumps work with strings
📌 Use pathlib.Path for cross-platform path handling — it's the modern approach
🟢 Read large files line-by-line with "for line in f:" to save memory
filesjsonio

Modules & Virtual Environments

Import modules, create packages, and manage dependencies with venv and pip.

Import standard library and custom modules; structure runnable scripts.

python
import os
from pathlib import Path
from datetime import datetime, timedelta
from collections import defaultdict, Counter
import json

# Alias
import numpy as np

# Import specific names
from math import sqrt, pi

# Script entry point — only runs when invoked directly,
# not when the module is imported
def main():
    print("Running as a script")

if __name__ == "__main__":
    main()
💡 A module is just a .py file — a package is a directory with __init__.py
⚡ Always wrap script entry logic in if __name__ == "__main__": so imports stay clean
📌 Avoid "from x import *" in production — it makes it unclear where names come from
🟢 Use relative imports (from . import) inside packages, absolute imports elsewhere
importsmodulespackages

Isolate project dependencies with venv, pip, or the modern uv toolchain.

python
# === Modern: uv (Astral) — fastest, all-in-one ===
# Install uv: https://docs.astral.sh/uv/

uv init my-project          # Create new project (pyproject.toml + venv)
cd my-project
uv add requests flask       # Install + lock deps
uv run python app.py        # Run inside the project env
uv sync                     # Install from uv.lock

# === Classic: venv + pip ===
python3 -m venv venv

# Activate
source venv/bin/activate    # macOS / Linux
venv\Scripts\activate       # Windows

pip install requests flask
pip freeze > requirements.txt
pip install -r requirements.txt
deactivate
💡 uv replaces pip + venv + pip-tools + pyenv — much faster, single tool
⚡ Always use a venv per project — never install globally
📌 pyproject.toml is the modern config; requirements.txt still works fine
🟢 Commit uv.lock (or pip-tools requirements.txt) so installs are reproducible

Advanced Features

Decorators, generators, context managers, and the walrus operator.

Decorators

Wrap functions to add behavior without modifying the original.

python
import functools

def timer(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        import time
        start = time.time()
        result = func(*args, **kwargs)
        print(f"{func.__name__}: {time.time()-start:.2f}s")
        return result
    return wrapper

@timer
def slow_function():
    import time; time.sleep(1)
💡 Always use @functools.wraps(func) to preserve the original function's metadata
⚡ @functools.lru_cache is a built-in memoization decorator — use it for expensive calls
📌 Stacked decorators apply bottom-up: the lowest decorator wraps first
🟢 The @decorator syntax is shorthand for: func = decorator(func)
decoratorsadvanced

Lazy iteration with yield and resource management with "with" statements.

python
# Generator function
def countdown(n):
    while n > 0:
        yield n
        n -= 1

for num in countdown(5):
    print(num)  # 5, 4, 3, 2, 1

# Context manager
with open("file.txt") as f:
    data = f.read()
💡 Generators are memory-efficient — they compute values on demand, not all at once
⚡ Use next() to get the next value from a generator manually
📌 Context managers guarantee cleanup — even if an exception occurs inside the block
🟢 Use @contextmanager from contextlib to create simple context managers with yield
generatorsyieldcontext-managerswith

Async & Await

Cooperative concurrency for I/O-bound work (network, files, DBs).

python
import asyncio

# Define an async function (coroutine)
async def fetch_user(user_id: int) -> dict:
    await asyncio.sleep(1)   # Simulate network call
    return {"id": user_id, "name": "Alice"}

# Run an async function from sync code
async def main():
    user = await fetch_user(1)
    print(user)

asyncio.run(main())

# Run many concurrently
async def fetch_all():
    results = await asyncio.gather(
        fetch_user(1),
        fetch_user(2),
        fetch_user(3),
    )
    return results
💡 Async is for I/O-bound work (network, files, DBs) — not CPU-bound work
⚡ asyncio.gather() runs coroutines concurrently; await runs them one at a time
📌 Never call a coroutine without await — you get a warning, not a result
🟢 Use asyncio.to_thread() to run a blocking sync function without blocking the loop

Standard Library

Essential modules from the Python standard library.

Most commonly used standard library modules.

python
from collections import Counter, defaultdict
from datetime import datetime, timedelta
from pathlib import Path
import os, re, math, subprocess

Counter(["a","b","a"]).most_common()
now = datetime.now()
Path("dir").mkdir(exist_ok=True)
subprocess.run(["ls", "-la"], check=True)
💡 Counter is the fastest way to count occurrences — use it instead of manual loops
⚡ defaultdict(list) lets you append to missing keys without checking first
📌 Use pathlib.Path over os.path — it is more readable and cross-platform
🟢 subprocess.run(..., check=True, capture_output=True, text=True) is the modern default
stdlibcollectionsdatetimepathlibosre

Logging

Structured logging instead of print() for real applications.

python
import logging

# Basic setup — call once at app start
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
)

log = logging.getLogger(__name__)

log.debug("Detailed diagnostic info")
log.info("Normal event happened")
log.warning("Something looks off")
log.error("Something failed")
log.exception("Failed with traceback")  # Inside an except block
💡 Use logging in real apps; print() is for one-off scripts
⚡ Pass args (log.info("hi %s", name)) instead of formatting upfront — it is lazy
📌 log.exception() inside except blocks auto-includes the traceback
🟢 Use logging.getLogger(__name__) per module so you can tune levels per package

Best Practices

Pythonic patterns and performance tips.

Write idiomatic Python code.

python
# Swap variables
a, b = b, a

# Ternary
x = "yes" if condition else "no"

# Check empty
if not items:   # not len(items) == 0

# EAFP over LBYL
try:
    value = d[key]
except KeyError:
    value = default
💡 EAFP (try/except) is more Pythonic than LBYL (check before acting)
⚡ Use _ as a throwaway variable for values you don't need
📌 "Explicit is better than implicit" — readability counts (PEP 20)
🟢 Run "import this" in the REPL to read The Zen of Python
best-practicespythonicpatterns