Skip to main content
Single-page reference · Python 3.10+

Python Cheatsheet for University Coursework

Every Python construct you reach for during a homework session, one tab away. Data types, control flow, functions, comprehensions, OOP, and the 8 stdlib patterns that pay back the most lines. Each snippet runs unedited on Python 3.10 or newer with the standard library alone, so you paste, run, adapt. No imports beyond what is shown. No external packages.

35 runnable snippets Stdlib only Python 3.10 to 3.13 compatible
Section 1 of 6

10 built-in data types with their common gotcha

The 10 types every CS50P, CS106A, and DATA 100 assignment touches. int, float, str, bool, None, list, tuple, dict, set, frozenset. Each card carries a literal, two or three useful methods, and the trap students hit on autograders.

int

Arbitrary precision integer. No 32 or 64 bit ceiling. Floor division uses //, modulo uses %.

Snippet
                  
                    # int literals and operators
x = 42
y = 0b1010        # binary literal = 10
z = 0xFF          # hex literal = 255
print(x // 5)     # 8  (floor division)
print(x % 5)      # 2  (modulo)
print(2 ** 100)   # 1267650600228229401496703205376  (no overflow)
# Gotcha: int / int returns float, not int
print(10 / 3)     # 3.3333333333333335
                  
                

float

IEEE 754 double. Equality comparisons fail on rounding. Use math.isclose for tolerant compare.

Snippet
                  
                    import math
a = 0.1 + 0.2
print(a)                      # 0.30000000000000004
print(a == 0.3)               # False
print(math.isclose(a, 0.3))   # True
print(float('inf'))           # inf
print(math.isnan(float('nan')))  # True
# Gotcha: never test float equality with ==
                  
                

str

Immutable Unicode text. f-strings since 3.6, debug-printing with f"{x=}" since 3.8.

Snippet
                  
                    name = 'Ada'
age = 36
print(f'{name} is {age}')          # Ada is 36
print(f'{age=}')                   # age=36
print('python'.upper())            # PYTHON
print('  trim me  '.strip())       # trim me
print('a,b,c'.split(','))          # ['a', 'b', 'c']
print('-'.join(['a', 'b', 'c']))   # a-b-c
# Gotcha: strings are immutable, mutation returns a new object
                  
                

bool

Subclass of int. True equals 1, False equals 0. Falsy values: 0, 0.0, "", [], {}, set(), None.

Snippet
                  
                    print(True + True)         # 2  (bool is an int subclass)
print(bool([]))            # False  (empty container is falsy)
print(bool('False'))       # True   (non-empty string is truthy)
print(bool(0))             # False
print(bool(None))          # False
# Gotcha: 'False' as a string is truthy; only the keyword False is falsy
                  
                

None

Singleton sentinel for absence. Compare with is, not ==. Default return for functions without return.

Snippet
                  
                    def noop():
    pass
result = noop()
print(result is None)      # True
print(result == None)      # True, but is None is preferred
# Gotcha: never use a mutable default argument
def bad(items=[]):         # shared across calls
    items.append(1)
    return items
print(bad())               # [1]
print(bad())               # [1, 1]  (state leaks)
                  
                

list

Mutable ordered sequence. Slicing returns a copy. Append is O(1) amortized, insert at 0 is O(n).

Snippet
                  
                    nums = [3, 1, 4, 1, 5]
nums.append(9)
nums.sort()
print(nums)                # [1, 1, 3, 4, 5, 9]
print(nums[-1])            # 9
print(nums[1:4])           # [1, 3, 4]   (slice is a copy)
print(sorted(nums, reverse=True))  # [9, 5, 4, 3, 1, 1]
# Gotcha: [0] * 3 makes 3 zeros, but [[]] * 3 makes 3 aliases of one list
grid = [[]] * 3
grid[0].append(1)
print(grid)                # [[1], [1], [1]]
                  
                

tuple

Immutable ordered sequence. Hashable when all elements are hashable. Used as dict keys and set elements.

Snippet
                  
                    point = (3, 4)
x, y = point               # tuple unpacking
print(x, y)                # 3 4
print(point[0])            # 3
print(len((1,)))           # 1  (trailing comma makes a 1-tuple)
seen = {(0, 0), (1, 1)}    # tuples as set members
print((0, 0) in seen)      # True
# Gotcha: (1) is an int, (1,) is a tuple
                  
                

dict

Hash map preserving insertion order since 3.7. Average O(1) for get, set, in. Keys must be hashable.

Snippet
                  
                    grades = {'Ada': 95, 'Bob': 82}
grades['Cy'] = 77
print(grades.get('Dan', 0))     # 0  (default if missing)
print('Ada' in grades)          # True
for name, score in grades.items():
    print(name, score)
merged = {**grades, 'Ed': 88}   # merge with unpacking
print(merged)
# Gotcha: dict.keys() returns a view, not a list, and tracks live changes
                  
                

set

Unordered unique hashable items. O(1) membership test. Union with |, intersection with &.

Snippet
                  
                    a = {1, 2, 3}
b = {3, 4, 5}
print(a | b)               # {1, 2, 3, 4, 5}  union
print(a & b)               # {3}              intersection
print(a - b)               # {1, 2}           difference
print(a ^ b)               # {1, 2, 4, 5}     symmetric diff
print(2 in a)              # True (O(1))
# Gotcha: {} is an empty dict, not an empty set; use set() for that
print(type({}))            # <class 'dict'>
print(type(set()))         # <class 'set'>
                  
                

frozenset

Immutable, hashable set. Can live inside another set or as a dict key. No add or remove.

Snippet
                  
                    tags = frozenset(['py', 'cs101'])
catalog = {tags: 'intro course'}
print(catalog[frozenset(['py', 'cs101'])])  # intro course
# Gotcha: frozenset has no .add(); construct it complete or use union to derive
extended = tags | {'fall2025'}
print(extended)            # frozenset({'py', 'cs101', 'fall2025'})
                  
                
Section 2 of 6

6 control-flow patterns from if through match

Branching and looping mechanics. The match statement landed in Python 3.10 and shows up on UC Berkeley CS61A, Stanford CS106A, and MIT 6.100L assignments since the 2023 update. The walrus operator pairs with while for clean sentinel reads.

if / elif / else

Walrus operator := assigns inside the test since 3.8. Match chains short-circuit left to right.

Snippet
                  
                    score = 87
if score >= 90:
    grade = 'A'
elif score >= 80:
    grade = 'B'
elif score >= 70:
    grade = 'C'
else:
    grade = 'F'
print(grade)               # B
# Walrus: assign and test in one line
data = [1, 2, 3]
if (n := len(data)) > 2:
    print(f'have {n} items')   # have 3 items
                  
                

for loop

Iterates any iterable. enumerate gives index and value. zip pairs two iterables and stops at the shorter.

Snippet
                  
                    for i, name in enumerate(['Ada', 'Bob', 'Cy'], start=1):
    print(i, name)
# 1 Ada
# 2 Bob
# 3 Cy
keys = ['x', 'y']
vals = [1, 2]
for k, v in zip(keys, vals):
    print(k, v)
# x 1
# y 2
# Gotcha: do not modify a list while iterating it; iterate a copy with list[:]
                  
                

while loop

Runs while the condition is truthy. Read a sentinel value with walrus to avoid duplication.

Snippet
                  
                    n = 10
while n > 0:
    print(n)
    n -= 1
# Walrus pattern: read until sentinel
lines = iter(['hi', 'bye', ''])
while (line := next(lines, '')):
    print(line)
# hi
# bye
                  
                

break / continue

break exits the nearest loop. continue jumps to the next iteration. Both apply to for and while.

Snippet
                  
                    for n in range(10):
    if n == 3:
        continue          # skip 3
    if n == 7:
        break             # stop at 7
    print(n)
# 0 1 2 4 5 6
                  
                

else on loops

Loop else runs when the loop exits without break. Useful for search patterns.

Snippet
                  
                    def find(seq, target):
    for x in seq:
        if x == target:
            print('found')
            break
    else:
        print('not found')
find([1, 2, 3], 2)        # found
find([1, 2, 3], 9)        # not found
                  
                

match / case (3.10+)

Structural pattern matching. Match by literal, type, sequence, or mapping. Use | for alternatives.

Snippet
                  
                    def classify(value):
    match value:
        case 0:
            return 'zero'
        case int() if value < 0:
            return 'negative int'
        case [x, y]:
            return f'pair ({x}, {y})'
        case {'name': name}:
            return f'record for {name}'
        case _:
            return 'other'
print(classify(0))                  # zero
print(classify(-5))                 # negative int
print(classify([1, 2]))             # pair (1, 2)
print(classify({'name': 'Ada'}))    # record for Ada
                  
                
Section 3 of 6

6 function patterns from defaults to decorators

Function signatures lose marks fast on autograders that strict-check argument names. The mutable-default trap below has appeared on at least 4 Gradescope-graded CS50P problem sets since 2022. Type hints satisfy the optional mypy steps in CSE 163 and DATA 100 grading.

def with default args

Defaults bind at function definition time. Never use a mutable default. Use None and rebind inside.

Snippet
                  
                    def greet(name, greeting='hi'):
    return f'{greeting}, {name}'
print(greet('Ada'))             # hi, Ada
print(greet('Bob', 'hello'))    # hello, Bob

# Correct pattern for mutable default
def add_item(item, basket=None):
    if basket is None:
        basket = []
    basket.append(item)
    return basket
print(add_item(1))              # [1]
print(add_item(2))              # [2]  (fresh list each call)
                  
                

*args and **kwargs

*args packs positional extras into a tuple. **kwargs packs keyword extras into a dict. Order in signature is positional, *args, keyword-only, **kwargs.

Snippet
                  
                    def stats(*nums, **opts):
    label = opts.get('label', 'stats')
    return f'{label}: count={len(nums)}, sum={sum(nums)}'
print(stats(1, 2, 3))                    # stats: count=3, sum=6
print(stats(1, 2, 3, label='scores'))    # scores: count=3, sum=6
# Unpack a list and a dict at the call site
nums = [10, 20, 30]
opts = {'label': 'totals'}
print(stats(*nums, **opts))              # totals: count=3, sum=60
                  
                

type hints

Annotations document intent and feed mypy. Runtime ignores them by default. Use list[int] not List[int] on 3.9+.

Snippet
                  
                    def average(scores: list[float]) -> float:
    return sum(scores) / len(scores)

from typing import Optional
def find_user(uid: int) -> Optional[str]:
    table = {1: 'Ada', 2: 'Bob'}
    return table.get(uid)
print(average([90.0, 80.0, 70.0]))    # 80.0
print(find_user(1))                   # Ada
print(find_user(9))                   # None
                  
                

lambda

Single-expression anonymous function. Useful as a key argument to sorted, min, max, filter.

Snippet
                  
                    points = [(1, 5), (3, 2), (2, 8)]
points.sort(key=lambda p: p[1])
print(points)            # [(3, 2), (1, 5), (2, 8)]
square = lambda x: x * x
print(square(7))         # 49
# Lambda captures variables by reference; use default-arg trick to freeze
funcs = [lambda x, i=i: x + i for i in range(3)]
print([f(10) for f in funcs])    # [10, 11, 12]
                  
                

scope and closure

LEGB resolution: Local, Enclosing, Global, Built-in. Use nonlocal to write to an enclosing scope, global for module scope.

Snippet
                  
                    def make_counter():
    count = 0
    def increment():
        nonlocal count
        count += 1
        return count
    return increment

c = make_counter()
print(c())               # 1
print(c())               # 2
print(c())               # 3
                  
                

decorator basics

A decorator wraps a function with another function. functools.wraps preserves the original name and docstring.

Snippet
                  
                    import functools, time

def timed(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        t0 = time.perf_counter()
        result = func(*args, **kwargs)
        dt = time.perf_counter() - t0
        print(f'{func.__name__} took {dt*1000:.3f} ms')
        return result
    return wrapper

@timed
def work(n):
    return sum(range(n))

print(work(100000))     # work took ~1 ms ... then 4999950000
                  
                
Section 4 of 6

5 comprehension and generator patterns

Comprehensions cut three-line loops to one expression and run inside the C layer of the CPython interpreter, which makes them faster than the equivalent for loop by 10 to 30 percent on common benchmarks. Generators stream values lazily, which keeps memory flat even on a million-row dataset.

list comprehension

Build a list inline. Replaces three-line loops with one expression. Add a guard with if.

Snippet
                  
                    squares = [x * x for x in range(6)]
print(squares)                # [0, 1, 4, 9, 16, 25]
evens = [x for x in range(10) if x % 2 == 0]
print(evens)                  # [0, 2, 4, 6, 8]
# Nested loop reads outer-first
pairs = [(a, b) for a in [1, 2] for b in ['x', 'y']]
print(pairs)                  # [(1, 'x'), (1, 'y'), (2, 'x'), (2, 'y')]
                  
                

dict and set comprehension

Same syntax with key:value for dict and bare expression for set. Faster than dict(zip(...)) in many cases.

Snippet
                  
                    square_map = {x: x * x for x in range(5)}
print(square_map)             # {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}
unique_lengths = {len(w) for w in ['hi', 'hello', 'hey', 'hola']}
print(unique_lengths)         # {2, 5, 3, 4}
# Invert a dict
codes = {'red': 1, 'green': 2, 'blue': 3}
by_code = {v: k for k, v in codes.items()}
print(by_code)                # {1: 'red', 2: 'green', 3: 'blue'}
                  
                

generator expression

Lazy iterable. No memory cost for the full sequence. Use when you only consume once.

Snippet
                  
                    gen = (x * x for x in range(10**6))
print(sum(gen))               # 333332833333500000
# A generator is exhausted after iteration
nums = (x for x in [1, 2, 3])
print(list(nums))             # [1, 2, 3]
print(list(nums))             # []  (already consumed)
# Drop the parens inside a single-arg function call
print(sum(x * x for x in range(5)))   # 30
                  
                

walrus in comprehensions

Assign once inside a comprehension, then reuse. Avoids calling an expensive function twice per element.

Snippet
                  
                    def slow_square(x):
    return x * x

results = [y for x in range(5) if (y := slow_square(x)) > 4]
print(results)                # [9, 16]
                  
                

yield and generator function

A function with yield returns a generator. Each next() resumes after the last yield. Use for streaming data and infinite sequences.

Snippet
                  
                    def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

fib = fibonacci()
first_ten = [next(fib) for _ in range(10)]
print(first_ten)              # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
# yield from delegates to another generator
def chained():
    yield from range(3)
    yield from 'ab'
print(list(chained()))        # [0, 1, 2, 'a', 'b']
                  
                
Section 5 of 6

5-piece OOP one-pager from class to dataclass

Object-oriented Python collapses to 5 concepts that cover 95 percent of homework needs. class definitions, the three method flavors, inheritance through super, properties for computed fields, and dataclasses for boilerplate-free records. CMU 15-112 and Harvard CS50P both grade dataclass usage.

class and __init__

__init__ runs at instantiation. self is the instance reference. Class body runs once at class creation, not per instance.

Snippet
                  
                    class Student:
    school = 'DMPH'                  # class attribute, shared

    def __init__(self, name, gpa):
        self.name = name             # instance attribute
        self.gpa = gpa

    def __repr__(self):
        return f'Student({self.name!r}, {self.gpa})'

s = Student('Ada', 3.9)
print(s)                              # Student('Ada', 3.9)
print(s.school)                       # DMPH
print(Student.school)                 # DMPH
                  
                

instance, class, static methods

@classmethod takes the class as first arg. @staticmethod takes nothing implicit. Pick by what the method needs.

Snippet
                  
                    class Temperature:
    def __init__(self, celsius):
        self.celsius = celsius

    def to_fahrenheit(self):                # instance method
        return self.celsius * 9 / 5 + 32

    @classmethod
    def from_fahrenheit(cls, f):            # alternative constructor
        return cls((f - 32) * 5 / 9)

    @staticmethod
    def is_freezing(celsius):               # no self, no cls
        return celsius <= 0

t = Temperature.from_fahrenheit(100)
print(t.to_fahrenheit())                    # 100.0
print(Temperature.is_freezing(-5))          # True
                  
                

inheritance and super()

Subclass inherits methods and attributes. super() calls the parent without naming it, which keeps multiple inheritance and refactors safe.

Snippet
                  
                    class Animal:
    def __init__(self, name):
        self.name = name

    def speak(self):
        return 'generic sound'

class Dog(Animal):
    def __init__(self, name, breed):
        super().__init__(name)
        self.breed = breed

    def speak(self):
        return f'{self.name} barks'

d = Dog('Rex', 'Husky')
print(d.speak())                # Rex barks
print(isinstance(d, Animal))    # True
                  
                

@property

Computed attribute that looks like a field. Add @x.setter for write access. Cleanest way to add validation without breaking callers.

Snippet
                  
                    class Circle:
    def __init__(self, radius):
        self._radius = radius

    @property
    def radius(self):
        return self._radius

    @radius.setter
    def radius(self, value):
        if value < 0:
            raise ValueError('radius must be non-negative')
        self._radius = value

    @property
    def area(self):
        return 3.14159 * self._radius ** 2

c = Circle(5)
print(c.area)                # 78.53975
c.radius = 10
print(c.area)                # 314.159
                  
                

dataclass

@dataclass auto-generates __init__, __repr__, __eq__. frozen=True makes instances immutable and hashable.

Snippet
                  
                    from dataclasses import dataclass, field

@dataclass(frozen=True)
class Point:
    x: float
    y: float
    label: str = ''

@dataclass
class Inventory:
    items: list = field(default_factory=list)

p = Point(3.0, 4.0, label='origin offset')
print(p)                            # Point(x=3.0, y=4.0, label='origin offset')
print(hash(p))                      # works because frozen=True

inv = Inventory()
inv.items.append('pencil')
print(inv)                          # Inventory(items=['pencil'])
                  
                
Section 6 of 6

8 stdlib patterns students reach for

The 8 modules that earn the most lines saved per import: collections, itertools, functools, pathlib, json, re, and datetime. Each pattern below appears at least once per term in DATA 100, CSE 163, CS50P, and CSE 160 problem sets. Memorize the signature and you cut typing in half.

collections.Counter

Counts hashable items in one pass. most_common(n) returns the top n pairs sorted by frequency.

Snippet
                  
                    from collections import Counter
text = 'the quick brown fox jumps over the lazy dog the end'
counts = Counter(text.split())
print(counts)                        # Counter({'the': 3, 'quick': 1, ...})
print(counts.most_common(2))         # [('the', 3), ('quick', 1)]
# Arithmetic on Counters
a = Counter(a=3, b=1)
b = Counter(a=1, b=2)
print(a + b)                         # Counter({'a': 4, 'b': 3})
print(a - b)                         # Counter({'a': 2})  (negatives dropped)
                  
                

collections.defaultdict

Auto-creates a default value on missing key. Bucket grouping in one line, no setdefault dance.

Snippet
                  
                    from collections import defaultdict
groups = defaultdict(list)
records = [('A', 1), ('B', 2), ('A', 3), ('B', 4)]
for key, value in records:
    groups[key].append(value)
print(dict(groups))                  # {'A': [1, 3], 'B': [2, 4]}
# defaultdict(int) for a counting dict without Counter
tally = defaultdict(int)
for word in 'a b a b a c'.split():
    tally[word] += 1
print(dict(tally))                   # {'a': 3, 'b': 2, 'c': 1}
                  
                

itertools.chain, groupby, combinations

chain flattens iterables. groupby clusters consecutive equal items (sort first if groups are not consecutive). combinations gives k-subsets.

Snippet
                  
                    import itertools as it
print(list(it.chain([1, 2], [3, 4], [5])))     # [1, 2, 3, 4, 5]

data = sorted(['apple', 'ant', 'bee', 'banana'], key=lambda w: w[0])
for letter, words in it.groupby(data, key=lambda w: w[0]):
    print(letter, list(words))
# a ['ant', 'apple']
# b ['banana', 'bee']

print(list(it.combinations('abc', 2)))         # [('a','b'), ('a','c'), ('b','c')]
print(list(it.permutations('abc', 2)))         # 6 ordered pairs
                  
                

functools.lru_cache

Memoizes pure functions. Hashable args required. maxsize=None caches forever; pick a finite number to bound memory.

Snippet
                  
                    from functools import lru_cache

@lru_cache(maxsize=128)
def fib(n):
    if n < 2:
        return n
    return fib(n - 1) + fib(n - 2)

print(fib(50))                       # 12586269025  (fast thanks to cache)
print(fib.cache_info())              # CacheInfo(hits=48, misses=51, ...)
                  
                

pathlib.Path

Cross-platform path object. Replaces os.path string juggling. Supports / for joining, .read_text and .write_text for one-shot I/O.

Snippet
                  
                    from pathlib import Path
p = Path('data') / 'students.txt'
p.parent.mkdir(parents=True, exist_ok=True)
p.write_text('Ada,95\nBob,82\n')
print(p.exists())                    # True
print(p.read_text().splitlines())    # ['Ada,95', 'Bob,82']
print(p.suffix)                      # .txt
print(p.stem)                        # students
print(p.with_suffix('.csv'))         # data/students.csv
                  
                

json.loads and json.dumps

Serializes Python primitives. Pass indent for pretty output, ensure_ascii=False for unicode, default= for custom encoders.

Snippet
                  
                    import json
payload = {'name': 'Ada', 'scores': [95, 88, 91]}
serialized = json.dumps(payload, indent=2)
print(serialized)
# {
#   "name": "Ada",
#   "scores": [95, 88, 91]
# }
restored = json.loads(serialized)
print(restored['scores'])            # [95, 88, 91]
# Gotcha: json keys are always strings. {1: 'a'} round-trips as {'1': 'a'}
                  
                

re.search and re.findall

search returns the first match object or None. findall returns every non-overlapping match as a list. Use raw strings r"..." for backslashes.

Snippet
                  
                    import re
text = 'order 42, order 1337, total 9999'
m = re.search(r'order (\d+)', text)
print(m.group(1))                    # 42
print(re.findall(r'\d+', text))      # ['42', '1337', '9999']
# Named groups for readable extraction
m = re.search(r'(?P<user>\w+)@(?P<host>\S+)', 'send to ada@example.com today')
print(m.groupdict())                 # {'user': 'ada', 'host': 'example.com'}
                  
                

datetime basics

datetime.now() returns local naive time. Always pass tz= for production code. strftime formats; strptime parses.

Snippet
                  
                    from datetime import datetime, timedelta, timezone
now = datetime.now(timezone.utc)
print(now.isoformat())               # 2026-05-15T19:33:00+00:00 (example)
later = now + timedelta(days=7, hours=3)
print((later - now).total_seconds()) # 615600.0
parsed = datetime.strptime('2026-05-15', '%Y-%m-%d')
print(parsed.year, parsed.month, parsed.day)  # 2026 5 15
print(parsed.strftime('%A'))         # Friday
                  
                

Stuck on a Python assignment?

Send the brief for a fixed quote in 15 minutes. Tested code, a written walkthrough, and a named Python developer. Pay 50% to start, 50% after the code runs on your data.