Post

Python Fundamentals Cheatsheet

Personal cheatsheet for Python fundamentals. Covers data types, collections, functions, OOP, exceptions, type hints, dataclasses, concurrency, and the standard library.

1. Data Types & Variables

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
x: int = 42
y: float = 3.14
z: complex = 1 + 2j
b: bool = True        # True/False (capitalised)
n = None

# Type casting
int("42")             # 42
float("3.14")         # 3.14
str(42)               # "42"
bool(0)               # False
bool("hello")         # True

# Falsy values: 0, 0.0, "", [], {}, set(), None, False

# Multiple assignment
a, b, c = 1, 2, 3
a, *rest = [1, 2, 3, 4]   # a=1, rest=[2, 3, 4]

Python is dynamically typed - types are checked at runtime. Every variable is a reference to an object.


2. Strings

2.1. Literals & f-strings

1
2
3
4
5
6
7
8
9
10
11
12
13
s = "hello"
s = 'hello'
s = """
multi
line
"""

name = "Ryo"
age = 25
f"{name} is {age}"            # "Ryo is 25"
f"{3.14159:.2f}"               # "3.14"
f"{1000000:,}"                 # "1,000,000"
f"{'hello':>10}"               # "     hello"

2.2. Common Methods

1
2
3
4
5
6
7
8
9
10
11
12
13
14
s = "  Hello, World!  "

s.strip()                       # "Hello, World!"
s.lstrip() / s.rstrip()
s.lower() / s.upper()
s.title()                       # "Hello, World!" (capitalises each word)
s.replace("World", "Python")    # "  Hello, Python!  "
s.split(", ")                   # ["  Hello", "World!  "]
", ".join(["a", "b", "c"])      # "a, b, c"
s.startswith("  H")             # True
s.find("World")                 # 9  (-1 if not found)
s.count("l")                    # 3
"  ".isspace()                  # True
"123".isdigit()                 # True

2.3. Slicing

1
2
3
4
5
6
s = "hello"
s[0]       # 'h'
s[-1]      # 'o'
s[1:3]     # 'el'
s[::2]     # 'hlo'   (every 2nd char)
s[::-1]    # 'olleh' (reverse)

Strings are immutable - slicing returns a new string.


3. Collections

3.1. List

Ordered, mutable, allows duplicates.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
lst = [1, 2, 3]
lst.append(4)
lst.extend([5, 6])
lst.insert(0, 0)          # insert value at index
lst.remove(3)             # remove first occurrence of value
lst.pop()                 # remove and return last
lst.pop(0)                # remove and return at index
lst.sort()                # in-place
sorted(lst)               # returns new sorted list
lst.reverse()
lst.index(2)              # index of first occurrence
len(lst)
2 in lst                  # True
lst[1:3]                  # slice

3.2. Tuple

Ordered, immutable.

1
2
3
4
5
6
t = (1, 2, 3)
t = 1, 2, 3       # parentheses optional
t = (1,)          # single-element - trailing comma required
a, b, c = t       # unpacking

# Tuples are hashable if all elements are hashable - can be used as dict keys

3.3. Set

Unordered, no duplicates.

1
2
3
4
5
6
7
8
9
10
11
12
13
s = {1, 2, 3}
s = set()           # empty set ({} creates a dict)
s.add(4)
s.discard(99)       # no error if missing
s.remove(1)         # KeyError if missing
3 in s              # O(1)

a = {1, 2, 3}
b = {2, 3, 4}
a | b               # union:                {1, 2, 3, 4}
a & b               # intersection:         {2, 3}
a - b               # difference:           {1}
a ^ b               # symmetric difference: {1, 4}

3.4. Dictionary

Key-value pairs, insertion-ordered (Python 3.7+).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
d = {"a": 1, "b": 2}
d["a"]                   # 1 - KeyError if missing
d.get("z", 0)            # 0 if missing
d["c"] = 3
del d["a"]
d.pop("b")               # remove and return
"a" in d
d.keys() / d.values() / d.items()
d.update({"d": 4})

# defaultdict - auto-creates missing keys
from collections import defaultdict
dd = defaultdict(list)
dd["x"].append(1)        # no KeyError

# Counter
from collections import Counter
Counter("aabbcc")                  # Counter({'a': 2, 'b': 2, 'c': 2})
Counter([1,1,2,3]).most_common(2)  # [(1, 2), (2, 1)]

3.5. Comprehensions

1
2
3
4
5
6
7
8
9
10
11
12
# List
squares = [x**2 for x in range(10)]
evens   = [x for x in range(20) if x % 2 == 0]

# Dict
lengths = {word: len(word) for word in ["hi", "hello"]}

# Set
unique_lens = {len(w) for w in ["hi", "hello", "hey"]}

# Generator - lazy, does not build list in memory
total = sum(x**2 for x in range(1_000_000))

4. Control Flow

Python control flow includes if/elif/else, for, while, and match/case (Python 3.10+). for/while loops support an else clause that runs when the loop exits without a break.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# if / elif / else
if x > 0:
    print("positive")
elif x < 0:
    print("negative")
else:
    print("zero")

# Ternary
result = "pos" if x > 0 else "non-pos"

# for
for item in [1, 2, 3]:
    print(item)

for i, item in enumerate(["a", "b", "c"]):
    print(i, item)    # 0 a, 1 b, 2 c

for k, v in d.items():
    print(k, v)

for a, b in zip([1, 2], ["x", "y"]):
    print(a, b)       # 1 x, 2 y

for i in range(5):         # 0-4
    pass
for i in range(2, 10, 2):  # 2, 4, 6, 8
    pass

# while
while condition:
    if should_stop: break
    if should_skip: continue
else:
    # runs if loop completed without a break
    pass

# match (Python 3.10+)
match command:
    case "quit":
        quit()
    case "go" | "move":
        move()
    case {"action": action, "target": target}:   # dict pattern
        handle(action, target)
    case _:
        print("unknown")

5. Functions

5.1. Definition & Arguments

Python functions support positional, default, *args (variadic positional), **kwargs (variadic keyword), keyword-only (after *), and positional-only (before /) parameters.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def greet(name: str, greeting: str = "Hello") -> str:
    return f"{greeting}, {name}!"

# *args - variable positional args (tuple)
def add(*numbers: int) -> int:
    return sum(numbers)

# **kwargs - variable keyword args (dict)
def create(**fields):
    return fields

# Keyword-only (must be passed by name)
def func(a, b, *, must_be_keyword):
    pass

# Positional-only (before /)
def func(a, b, /, c):   # a and b cannot be passed as kwargs
    pass

5.2. Closures & nonlocal

A closure captures variables from the enclosing scope. Use nonlocal to rebind an enclosing variable rather than just read it.

1
2
3
4
5
6
7
8
9
10
11
def make_counter():
    count = 0
    def increment():
        nonlocal count
        count += 1
        return count
    return increment

counter = make_counter()
counter()   # 1
counter()   # 2

5.3. Decorators

A decorator wraps a function to extend its behavior without modifying it. Use @functools.wraps to preserve the wrapped function’s __name__ and __doc__.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import functools

def log(func):
    @functools.wraps(func)    # preserves __name__, __doc__
    def wrapper(*args, **kwargs):
        print(f"calling {func.__name__}")
        result = func(*args, **kwargs)
        print("done")
        return result
    return wrapper

@log
def say_hello(name):
    print(f"Hello {name}")

# Decorator with arguments
def repeat(n):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for _ in range(n):
                func(*args, **kwargs)
        return wrapper
    return decorator

@repeat(3)
def hello():
    print("hi")

5.4. Lambda

1
2
add = lambda x, y: x + y
sorted(users, key=lambda u: u.age)

6. Classes & OOP

6.1. Basics

Class variables are shared across all instances. Instance variables are set in __init__ via self. Override __repr__, __eq__, and __hash__ to control how objects behave in collections and comparisons.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Animal:
    species = "Unknown"   # class variable - shared across instances

    def __init__(self, name: str, age: int):
        self.name = name  # instance variable
        self.age = age

    def speak(self) -> str:
        return f"{self.name} makes a sound"

    def __repr__(self) -> str:
        return f"Animal(name={self.name!r}, age={self.age})"

    def __str__(self) -> str:
        return self.name

    def __eq__(self, other) -> bool:
        return isinstance(other, Animal) and self.name == other.name

    def __hash__(self):   # required if __eq__ is defined
        return hash(self.name)

6.2. Inheritance

Call super().__init__() to invoke the parent constructor. isinstance checks the full MRO, so isinstance(Dog(...), Animal) returns True.

1
2
3
4
5
6
7
8
9
10
class Dog(Animal):
    def __init__(self, name: str, age: int, breed: str):
        super().__init__(name, age)
        self.breed = breed

    def speak(self) -> str:
        return f"{self.name} barks"

isinstance(Dog("Rex", 3, "Lab"), Animal)   # True
issubclass(Dog, Animal)                    # True

6.3. @classmethod, @staticmethod, @property

@classmethod is used for alternative constructors. @staticmethod is a plain utility with no implicit argument. @property makes a method accessible as an attribute with optional setter logic.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Circle:
    def __init__(self, radius: float):
        self._radius = radius

    @classmethod
    def from_diameter(cls, diameter: float) -> "Circle":
        return cls(diameter / 2)   # alternative constructor

    @staticmethod
    def validate_radius(r: float) -> bool:
        return r > 0               # utility, no access to cls or self

    @property
    def radius(self) -> float:
        return self._radius

    @radius.setter
    def radius(self, value: float):
        if value <= 0:
            raise ValueError("radius must be positive")
        self._radius = value

    @property
    def area(self) -> float:
        import math
        return math.pi * self._radius ** 2

6.4. Common Dunder Methods

MethodTriggered by
__init__obj = Class()
__repr__repr(obj), REPL display
__str__str(obj), print(obj)
__eq__obj == other
__hash__hash(obj), set/dict membership
__len__len(obj)
__getitem__obj[key]
__contains__x in obj
__iter__for x in obj
__enter__ / __exit__with obj:
__add__ / __mul__obj + other, obj * n
__lt__ / __le__ etc.<, <= etc.

7. Exceptions

7.1. Hierarchy (partial)

1
2
3
4
5
6
7
8
9
10
11
12
BaseException
├── SystemExit
├── KeyboardInterrupt
└── Exception
    ├── ValueError
    ├── TypeError
    ├── AttributeError
    ├── KeyError
    ├── IndexError
    ├── FileNotFoundError
    ├── IOError
    └── RuntimeError

7.2. try / except / else / finally

1
2
3
4
5
6
7
8
9
10
11
12
try:
    result = 10 / x
except ZeroDivisionError:
    print("division by zero")
except (TypeError, ValueError) as e:
    print(f"bad input: {e}")
except Exception as e:
    raise RuntimeError("unexpected") from e   # exception chaining
else:
    print("no exception")                     # only if no exception was raised
finally:
    print("always runs")

7.3. Custom Exceptions

Subclass Exception (or a shared base class) to define domain-specific exceptions that carry structured data alongside a message.

1
2
3
4
5
6
7
8
9
10
class AppError(Exception):
    pass

class NotFoundError(AppError):
    def __init__(self, resource: str, id: int):
        super().__init__(f"{resource} with id {id} not found")
        self.resource = resource
        self.id = id

raise NotFoundError("User", 42)

7.4. Context Managers

A context manager ties setup and teardown logic to a with block, guaranteeing cleanup even if an exception is raised. Implement __enter__/__exit__ on a class, or use @contextmanager on a generator function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# __enter__ and __exit__ are called automatically
with open("file.txt") as f:
    data = f.read()

# Custom context manager
from contextlib import contextmanager

@contextmanager
def db_transaction(conn):
    try:
        yield conn
        conn.commit()
    except Exception:
        conn.rollback()
        raise

8. Modules, Packages & Virtual Environments

8.1. Imports

1
2
3
4
5
6
7
8
import os
from os import path, getcwd
from os.path import join, exists
import numpy as np              # alias

# Relative (inside a package)
from . import sibling
from ..parent import something

8.2. Package Structure

1
2
3
4
5
6
my_package/
├── __init__.py        # makes the directory a package
├── module_a.py
└── sub/
    ├── __init__.py
    └── module_b.py

__init__.py can re-export symbols:

1
2
# my_package/__init__.py
from .module_a import MyClass   # consumers can import directly from my_package

8.3. __name__ Guard

1
2
3
4
5
def main():
    print("running")

if __name__ == "__main__":
    main()   # only runs when executed directly, not when imported

8.4. Virtual Environments

1
2
3
4
5
6
7
8
9
10
11
12
python -m venv .venv
source .venv/bin/activate     # macOS/Linux
.venv\Scripts\activate        # Windows
deactivate

pip install requests
pip freeze > requirements.txt
pip install -r requirements.txt

# Modern tooling
pip install uv
uv venv && uv pip install -r requirements.txt

9. File I/O

Use with blocks to ensure files are closed automatically. Always specify encoding explicitly to avoid platform-dependent behavior.

1
2
3
4
5
6
7
8
9
10
11
12
13
# Read
with open("file.txt", "r", encoding="utf-8") as f:
    content = f.read()          # whole file as string
    lines = f.readlines()       # list of lines
    for line in f:              # line-by-line (memory efficient)
        print(line.strip())

# Write / Append
with open("file.txt", "w", encoding="utf-8") as f:
    f.write("hello\n")

with open("file.txt", "a") as f:
    f.write("more\n")
ModeMeaning
rRead (default)
wWrite (truncates if exists)
aAppend
xCreate (fails if exists)
bBinary mode (rb, wb)
+Read + write (r+, w+)

9.1. pathlib

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pathlib import Path

p = Path("data") / "input" / "file.csv"
p.exists()
p.is_file()
p.is_dir()
p.suffix         # ".csv"
p.stem           # "file"
p.parent         # Path("data/input")
p.name           # "file.csv"
p.read_text(encoding="utf-8")
p.write_text("content")
p.mkdir(parents=True, exist_ok=True)
list(p.parent.glob("*.csv"))

10. Type Hints

Type hints are not enforced at runtime. They are used by static analysis tools like mypy and IDEs for completions and error detection.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from typing import Optional, Union, Any
from collections.abc import Callable, Sequence, Iterator

def greet(name: str) -> str: ...

# Optional - can be None
def find(id: int) -> str | None: ...     # Python 3.10+ union syntax
def find(id: int) -> Optional[str]: ...  # equivalent, older style

# Collections
def process(items: list[int]) -> dict[str, int]: ...

# Callable
def apply(fn: Callable[[int, str], bool], x: int) -> bool: ...

# TypeVar - generic functions
from typing import TypeVar
T = TypeVar("T")
def first(items: list[T]) -> T:
    return items[0]

# Protocol - structural typing (duck typing)
from typing import Protocol

class Drawable(Protocol):
    def draw(self) -> None: ...

def render(item: Drawable) -> None:
    item.draw()   # any object with a draw() method works - no inheritance needed
1
mypy my_module.py    # static type checker

11. Dataclasses & NamedTuples

11.1. @dataclass

Auto-generates __init__, __repr__, __eq__.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from dataclasses import dataclass, field

@dataclass
class Point:
    x: float
    y: float
    label: str = "origin"
    tags: list[str] = field(default_factory=list)   # mutable defaults need field()

p = Point(1.0, 2.0)
# Point(x=1.0, y=2.0, label='origin', tags=[])

@dataclass(frozen=True)    # immutable (generates __hash__)
class Coords:
    lat: float
    lon: float

@dataclass(order=True)     # generates __lt__, __le__ etc. based on field order
class Version:
    major: int
    minor: int

11.2. NamedTuple

1
2
3
4
5
6
7
8
9
10
11
from typing import NamedTuple

class Employee(NamedTuple):
    name: str
    dept: str
    salary: float = 0.0

e = Employee("Ryo", "Engineering", 80000)
e.name       # "Ryo"
e[0]         # "Ryo" (tuple access still works)
e._asdict()  # {"name": "Ryo", "dept": "Engineering", "salary": 80000}

12. Concurrency

12.1. Threading (I/O-bound)

1
2
3
4
5
6
7
8
9
10
11
12
13
import threading

def fetch(url):
    print(f"fetching {url}")

threads = [threading.Thread(target=fetch, args=(url,)) for url in urls]
for t in threads: t.start()
for t in threads: t.join()

# Thread-safe shared state
lock = threading.Lock()
with lock:
    shared_counter += 1

The GIL (Global Interpreter Lock) prevents true parallel CPU execution in CPython threads. Threading is still effective for I/O-bound work.

12.2. Multiprocessing (CPU-bound)

1
2
3
4
5
6
7
from multiprocessing import Pool

def square(x):
    return x * x

with Pool(processes=4) as pool:
    results = pool.map(square, range(10))   # [0, 1, 4, 9, ...]

Each process has its own interpreter and memory - no GIL restriction.

12.3. asyncio

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import asyncio

async def fetch_data(url: str) -> str:
    await asyncio.sleep(1)    # simulate async I/O
    return f"data from {url}"

async def main():
    # Sequential
    result = await fetch_data("https://api.example.com")

    # Concurrent - all three run simultaneously
    results = await asyncio.gather(
        fetch_data("https://api1.com"),
        fetch_data("https://api2.com"),
        fetch_data("https://api3.com"),
    )

    # With timeout
    result = await asyncio.wait_for(fetch_data("url"), timeout=5.0)

asyncio.run(main())

asyncio is single-threaded - cooperative multitasking. Use for I/O-bound async work (HTTP, DB, file). Use multiprocessing for CPU-bound.


13. Common Standard Library

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
import os
os.getcwd()
os.listdir(".")
os.environ.get("HOME")
os.path.join("a", "b", "c.txt")
os.makedirs("dir/sub", exist_ok=True)

import sys
sys.argv           # command-line args list
sys.exit(0)
sys.path           # module search paths

import json
json.dumps({"a": 1}, indent=2)    # dict to JSON string
json.loads('{"a": 1}')            # JSON string to dict
json.dump(data, file)             # write JSON to file
json.load(file)                   # read JSON from file

import re
re.match(r"^\d+", "123abc")        # match at start
re.search(r"\d+", "abc123")        # match anywhere
re.findall(r"\d+", "a1 b2 c3")    # ["1", "2", "3"]
re.sub(r"\s+", " ", "a  b   c")   # "a b c"

from datetime import datetime, date, timedelta
now = datetime.now()
today = date.today()
dt = datetime(2024, 1, 15, 10, 30)
dt.strftime("%Y-%m-%d %H:%M:%S")
datetime.strptime("2024-01-15", "%Y-%m-%d")
later = now + timedelta(days=7, hours=3)

import copy
copy.copy(obj)       # shallow copy
copy.deepcopy(obj)   # deep copy

from collections import OrderedDict, deque
d = deque(maxlen=100)    # efficient append/pop from both ends
d.appendleft(x)
d.popleft()

import itertools
list(itertools.chain([1,2], [3,4]))              # [1, 2, 3, 4]
list(itertools.product([1,2], ["a","b"]))        # [(1,'a'),(1,'b'),(2,'a'),(2,'b')]
list(itertools.combinations([1,2,3], 2))         # [(1,2),(1,3),(2,3)]
list(itertools.permutations([1,2,3], 2))

import functools
functools.reduce(lambda a, b: a + b, [1,2,3,4])  # 10
functools.lru_cache(maxsize=128)                  # memoize decorator
functools.partial(pow, 2)                         # partial(pow,2)(10) = 1024

Comments powered by Disqus.