Skip to content

Python Concepts

← Back to all decks

109 cards — 🟢 55 easy | 🟡 37 medium | 🔴 2 hard

🟢 Easy (55)

1. if / elif / else

Show answer x = 10
if x > 20:
print("big")
elif x > 5:
print("medium") # ← this runs
else:
print("small")

Conditions are evaluated top-to-bottom; first True branch wins. elif and else are optional.

2. for loop

Show answer for name in ["alice", "bob", "carol"]:
print(name.upper())

Iterates over any iterable: lists, strings, dicts, files, generators, range().

3. while loop

Show answer n = 5
while n > 0:
print(n)
n -= 1

Runs as long as the condition is True. Use break to exit early.

4. break

Show answer for n in range(100):
if n > 4:
break
print(n) # prints 0 1 2 3 4

Exits the innermost loop immediately.

5. continue

Show answer for n in range(5):
if n == 2:
continue
print(n) # prints 0 1 3 4

Skips the rest of the current iteration, jumps to the next one.

6. pass

Show answer class NotYet:
pass # placeholder — class body can't be empty

def todo():
pass # same for functions

Does nothing. Used as a syntactic placeholder.

7. def (function definition)

Show answer def greet(name):
return f"hello, {name}"

print(greet("alice")) # hello, alice

Functions are first-class objects — you can pass them, return them, store them.

8. lambda (anonymous function)

Show answer square = lambda x: x ** 2
print(square(5)) # 25

sorted(["bob", "alice"], key=lambda s: len(s))
# ['bob', 'alice']

Single-expression functions. No statements, no multi-line.

9. return

Show answer def add(a, b):
return a + b

result = add(3, 4) # 7

Sends a value back to the caller and exits the function. Without return, function returns None.

10. class

Show answer class Dog:
def __init__(self, name):
self.name = name
def speak(self):
return f"{self.name} says woof"

d = Dog("Rex")
print(d.speak()) # Rex says woof

11. self

Show answer class Counter:
def __init__(self):
self.count = 0
def inc(self):
self.count += 1

self is the instance — it's how methods access the object's data. Not a keyword, but always the first parameter.

12. try / except

Show answer try:
x = int("not a number")
except ValueError as e:
print(f"bad input: {e}")

Catch specific exceptions. Avoid bare except: — always name the exception type.

13. raise

Show answer def divide(a, b):
if b == 0:
raise ValueError("cannot divide by zero")
return a / b

Raise an exception explicitly. raise without args re-raises the current exception in an except block.

14. assert

Show answer def avg(nums):
assert len(nums) > 0, "empty list"
return sum(nums) / len(nums)

Raises AssertionError if condition is False. Disabled with python -O. Use for sanity checks, not input validation.

15. with (context manager)

Show answer with open("file.txt") as f:
data = f.read()
# f is automatically closed here

Guarantees cleanup (close, unlock, etc.) even if an exception occurs. Calls __enter__ and __exit__ under the hood.

16. as (aliasing)

Show answer import numpy as np # module alias
with open("f") as fh: # bind context manager result
except KeyError as e: # bind exception to name

as binds a name in import, with, and except statements.

17. in / not in (membership)

Show answer print(3 in [1, 2, 3]) # True
print("x" not in "hello") # True
print("key" in {"key": 1}) # True (checks keys)

Works on lists, tuples, sets, dicts (keys), strings (substring).

18. is / is not (identity)

Show answer x = None
if x is None:
print("nothing here")

a = [1, 2]
b = a
print(a is b) # True (same object)
print(a is [1,2]) # False (different object, same value)

is checks identity (same object in memory). Use == for value equality.

19. del (delete)

Show answer x = 42
del x # unbinds the name

lst = [1, 2, 3]
del lst[1] # [1, 3]

d = {"a": 1}
del d["a"] # {}

Removes a name binding, list element, dict entry, or attribute.

20. None

Show answer def find(lst, target):
for item in lst:
if item == target:
return item
return None # explicit "not found"

result = find([1,2,3], 99)
if result is None:
print("not found")

Python's null. There is exactly one None object. Always test with is None, not ==.

21. f-string (formatted string literal)

Show answer name = "alice"
age = 30
print(f"{name} is {age} years old")

print(f"{age:>10}") # right-align, width 10
print(f"{3.14159:.2f}") # 3.14
print(f"{1000:,}") # 1,000

Expressions inside {} are evaluated at runtime. Python 3.6+.

22. Common string methods

Show answer s = " Hello, World "
s.strip() # "Hello, World"
s.lower() # " hello, world "
s.split(", ") # [' Hello', 'World ']
"_".join(["a","b"]) # "a_b"
s.replace("World", "Python")
s.startswith(" He") # True
s.find("World") # 9 (-1 if not found)
"42".isdigit() # True

23. String slicing

Show answer s = "python"
s[0] # 'p'
s[-1] # 'n'
s[2:4] # 'th'
s[:3] # 'pyt'
s[3:] # 'hon'
s[::-1] # 'nohtyp' (reverse)
s[::2] # 'pto' (every other)

Same [start:stop:step] syntax works on lists and tuples.

24. List operations

Show answer a = [3, 1, 2]
a.append(4) # [3, 1, 2, 4]
a.extend([5, 6]) # [3, 1, 2, 4, 5, 6]
a.insert(0, 99) # [99, 3, 1, 2, 4, 5, 6]
a.pop() # returns 6, list shrinks
a.remove(99) # removes first occurrence
a.sort() # in-place: [1, 2, 3, 4, 5]
a.reverse() # in-place: [5, 4, 3, 2, 1]
len(a) # 5

25. List comprehension

Show answer squares = [x**2 for x in range(5)] # [0, 1, 4, 9, 16]
evens = [x for x in range(10) if x % 2 == 0] # [0, 2, 4, 6, 8]

flat = [c for word in ["hi","yo"] for c in word] # ['h','i','y','o']

More readable than map/filter for simple transforms.

26. Dict operations

Show answer d = {"a": 1, "b": 2}
d["c"] = 3 # add/update
d.get("z", 0) # 0 (no KeyError)
d.pop("b") # returns 2, removes key
d.update({"a": 10}) # merge
d.setdefault("d", 4) # insert only if missing

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

27. Dict comprehension

Show answer squares = {x: x**2 for x in range(5)}
# {0: 0, 1: 1, 2: 4, 3: 9, 4: 16}

inverse = {v: k for k, v in squares.items()}
# {0: 0, 1: 1, 4: 2, 9: 3, 16: 4}

28. Set operations

Show answer a = {1, 2, 3}
b = {2, 3, 4}

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

3 in a # True (O(1) lookup)
a.add(5)
a.discard(99) # no error if missing

29. Set comprehension

Show answer words = ["hello", "world", "hello"]
unique_lengths = {len(w) for w in words} # {5}

30. Tuple operations

Show answer t = (1, "two", 3.0)
t[0] # 1
t[-1] # 3.0
len(t) # 3
a, b, c = t # unpacking

# single-element tuple needs trailing comma
one = (42,)

# tuples are immutable — no append, no assignment
# t[0] = 99 → TypeError

31. enumerate()

Show answer for i, name in enumerate(["alice", "bob", "carol"]):
print(f"{i}: {name}")
# 0: alice
# 1: bob
# 2: carol

for i, ch in enumerate("abc", start=1):
print(i, ch) # 1 a, 2 b, 3 c

Replaces the manual counter pattern. Always prefer over range(len(x)).

32. zip()

Show answer names = ["alice", "bob"]
ages = [30, 25]

for name, age in zip(names, ages):
print(f"{name} is {age}")

dict(zip(names, ages)) # {"alice": 30, "bob": 25}

Stops at the shortest iterable. Use itertools.zip_longest to pad.

33. map() and filter()

Show answer nums = [1, 2, 3, 4, 5]

list(map(str, nums)) # ['1','2','3','4','5']
list(filter(lambda x: x>3, nums)) # [4, 5]

Both return lazy iterators. Usually prefer comprehensions:
[str(n) for n in nums]
[n for n in nums if n > 3]

34. any() and all()

Show answer nums = [2, 4, 6, 7, 8]
any(x > 5 for x in nums) # True (at least one)
all(x > 0 for x in nums) # True (every one)
all(x % 2 == 0 for x in nums) # False (7 is odd)

Short-circuit: any() stops at first True, all() stops at first False.

35. sorted() and .sort()

Show answer nums = [3, 1, 2]
sorted(nums) # [1, 2, 3] — returns new list
nums.sort() # in-place, returns None

words = ["banana", "apple", "cherry"]
sorted(words, key=len) # ['apple', 'banana', 'cherry']
sorted(words, key=len, reverse=True) # ['cherry', 'banana', 'apple']

sorted() works on any iterable. .sort() is list-only.

36. isinstance() and type()

Show answer x = 42
isinstance(x, int) # True
isinstance(x, (int, float)) # True (either type)
type(x) #
type(x) is int # True

Prefer isinstance() — it handles subclasses. type() is for exact type checks.

37. Conditional expression (ternary)

Show answer age = 20
status = "adult" if age >= 18 else "minor"

# In a list
labels = ["even" if x % 2 == 0 else "odd" for x in range(5)]
# ['even', 'odd', 'even', 'odd', 'even']

38. init and repr

Show answer class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return f"Point({self.x}, {self.y})"

p = Point(1, 2)
print(p) # Point(1, 2)

__init__ sets up the instance. __repr__ controls how it prints.

39. Inheritance

Show answer class Animal:
def speak(self):
return "..."

class Dog(Animal):
def speak(self):
return "woof"

class Cat(Animal):
def speak(self):
return "meow"

for a in [Dog(), Cat()]:
print(a.speak()) # woof, meow

Subclasses override parent methods. Use super() to call the parent.

40. collections.defaultdict

Show answer from collections import defaultdict

counts = defaultdict(int)
for word in "the cat sat on the mat".split():
counts[word] += 1
# {'the': 2, 'cat': 1, 'sat': 1, 'on': 1, 'mat': 1}

groups = defaultdict(list)
for name, dept in [("alice","eng"), ("bob","eng"), ("carol","hr")]:
groups[dept].append(name)
# {'eng': ['alice','bob'], 'hr': ['carol']}

41. collections.Counter

Show answer from collections import Counter

c = Counter("mississippi")
# Counter({'s': 4, 'i': 4, 'p': 2, 'm': 1})
c.most_common(2) # [('s', 4), ('i', 4)]

words = "the cat sat on the mat".split()
Counter(words) # Counter({'the': 2, 'cat': 1, ...})

42. pathlib basics

Show answer from pathlib import Path

p = Path("/var/log/syslog")
p.name # "syslog"
p.parent # Path("/var/log")
p.suffix # "" (no extension)
p.exists() # True/False
p.read_text() # file contents as string

# Build paths with /
conf = Path.home() / ".config" / "myapp"
conf.mkdir(parents=True, exist_ok=True)

# Glob
for f in Path("/var/log").glob("*.log"):
print(f)

43. subprocess basics

Show answer import subprocess

# Run a command, capture output
result = subprocess.run(
["ls", "-la"],
capture_output=True, text=True, check=True,
)
print(result.stdout)

# NEVER use shell=True with user input
# Always pass args as a list

44. JSON and YAML

Show answer import json

# JSON
data = {"name": "alice", "age": 30}
json.dumps(data) # string
json.dumps(data, indent=2) # pretty
json.loads('{"x": 1}') # dict

with open("f.json") as f:
data = json.load(f) # from file

# YAML (pip install pyyaml)
import yaml
yaml.safe_load(open("f.yaml"))
yaml.dump(data, default_flow_style=False)

45. datetime

Show answer from datetime import datetime, timedelta

now = datetime.now()
print(now.strftime("%Y-%m-%d %H:%M:%S"))

dt = datetime.strptime("2026-04-11", "%Y-%m-%d")

tomorrow = now + timedelta(days=1)
delta = datetime(2026, 12, 31) - now
print(f"{delta.days} days left")

46. os.environ

Show answer import os

home = os.environ["HOME"] # raises KeyError if missing
db = os.environ.get("DB_HOST", "localhost") # default if missing
os.environ["MY_VAR"] = "value" # set for this process

# List all env vars starting with AWS_
aws = {k: v for k, v in os.environ.items() if k.startswith("AWS_")}

47. shutil

Show answer import shutil

shutil.copy2("a.txt", "b.txt") # cp -p (preserves metadata)
shutil.copytree("src/", "dst/") # cp -r
shutil.move("old.txt", "new.txt") # mv
shutil.rmtree("/tmp/build") # rm -rf
shutil.which("python3") # /usr/bin/python3

usage = shutil.disk_usage("/")
print(f"{usage.free // 1e9:.0f} GB free")

48. tempfile

Show answer import tempfile

# Temp file (auto-deleted)
with tempfile.NamedTemporaryFile(mode="w", suffix=".txt") as f:
f.write("data")
print(f.name) # /tmp/tmpXXXX.txt

# Temp directory (auto-deleted)
with tempfile.TemporaryDirectory() as d:
print(d) # /tmp/tmpXXXXXX

49. secrets (secure random)

Show answer import secrets

# Secure token
tok = secrets.token_hex(16) # "a3f2...d1b7" (32 hex chars)
tok = secrets.token_urlsafe(16) # URL-safe base64

# Secure random integer
n = secrets.randbelow(100) # 0-99

# Secure password
import string
alpha = string.ascii_letters + string.digits
pwd = "".join(secrets.choice(alpha) for _ in range(16))

Use secrets, not random, for anything security-related.

50. hashlib

Show answer import hashlib

h = hashlib.sha256(b"hello world").hexdigest()
# "b94d27b9934d3e08a52e52d7da7dabfa..."

# Hash a file
def file_hash(path):
h = hashlib.sha256()
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(8192), b""):
h.update(chunk)
return h.hexdigest()

51. Augmented assignment

Show answer x = 10
x += 5 # 15
x -= 3 # 12
x *= 2 # 24
x //= 5 # 4
x **= 3 # 64
x %= 10 # 4

Also works with strings: s = "hi"; s += "!" → "hi!"

52. Truthy and falsy values

Show answer # Falsy values:
bool(0) # False
bool(0.0) # False
bool("") # False
bool([]) # False
bool({}) # False
bool(None) # False
bool(False) # False

# Everything else is truthy:
bool(1) # True
bool("hi") # True
bool([0]) # True (non-empty list)

# So you can write:
if my_list: # instead of if len(my_list) > 0

53. Multi-line strings and docstrings

Show answer # Triple quotes for multi-line
query = """
SELECT *
FROM users
WHERE active = true
"""

# Docstrings
def add(a, b):
"""Return the sum of a and b."""
return a + b

print(add.__doc__) # "Return the sum of a and b."

54. frozenset

Show answer a = frozenset([1, 2, 3])
# a.add(4) → AttributeError (immutable)

# Can use as dict key or set element
cache = {frozenset([1,2]): "result"}

# Same set operations work
a | frozenset([3, 4]) # frozenset({1, 2, 3, 4})

55. zip tricks

Show answer # Transpose a matrix
matrix = [(1,2,3), (4,5,6)]
list(zip(*matrix)) # [(1,4), (2,5), (3,6)]

# Unzip
pairs = [("a",1), ("b",2), ("c",3)]
keys, vals = zip(*pairs)
# keys = ('a','b','c'), vals = (1,2,3)

🟡 Medium (37)

1. yield (generators)

Show answer def countdown(n):
while n > 0:
yield n
n -= 1

for x in countdown(3):
print(x) # 3 2 1

Pauses the function and produces a value. Resumes on next iteration. Memory-efficient for large sequences.

2. try / except / else / finally

Show answer try:
f = open("data.txt")
except FileNotFoundError:
print("missing")
else:
print(f.read()) # runs only if no exception
finally:
print("always runs") # cleanup goes here

3. global / nonlocal

Show answer count = 0
def increment():
global count
count += 1

def outer():
x = 10
def inner():
nonlocal x
x += 1
inner()
print(x) # 11

global refers to module scope; nonlocal refers to enclosing function scope.

4. Unpacking ( and *)

Show answer # Iterable unpacking
first, *rest = [1, 2, 3, 4] # first=1, rest=[2,3,4]
a, _, b = (1, 2, 3) # ignore middle value

# Function argument unpacking
def f(a, b, c): return a + b + c
args = [1, 2, 3]
f(*args) # 6

kw = {"a": 1, "b": 2, "c": 3}
f(**kw) # 6

# Merge dicts
merged = {**d1, **d2}

5. Walrus operator (:=)

Show answer # Assign and test in one expression
if (n := len("hello")) > 3:
print(f"long: {n} chars") # long: 5 chars

# In a while loop
while (line := input("> ")) != "quit":
print(f"you said: {line}")

# In a comprehension
results = [y for x in data if (y := expensive(x)) > threshold]

Python 3.8+. Use sparingly — readability first.

6. match / case (structural pattern matching)

Show answer def handle(command):
match command.split():
case ["quit"]:
return "bye"
case ["go", direction]:
return f"going {direction}"
case ["pick", "up", item]:
return f"picked up {item}"
case _:
return "unknown"

handle("go north") # "going north"
handle("pick up key") # "picked up key"

Python 3.10+. Matches structure, not just values.

7. Decorator

Show answer def log_calls(fn):
def wrapper(*args, **kwargs):
print(f"calling {fn.__name__}")
return fn(*args, **kwargs)
return wrapper

@log_calls
def add(a, b):
return a + b

add(1, 2) # prints "calling add", returns 3

@decorator is syntactic sugar for: add = log_calls(add)

8. Closure

Show answer def make_counter(start=0):
count = start
def increment():
nonlocal count
count += 1
return count
return increment

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

The inner function captures variables from the enclosing scope. They persist after the outer function returns.

9. args and *kwargs

Show answer def f(*args, **kwargs):
print(args) # tuple of positional args
print(kwargs) # dict of keyword args

f(1, 2, x=3, y=4)
# (1, 2)
# {'x': 3, 'y': 4}

# Keyword-only args (after *):
def g(a, b, *, verbose=False):
pass # verbose must be passed by name

10. Type hints

Show answer def greet(name: str, times: int = 1) -> str:
return (f"hello {name}\
") * times

from typing import Optional
def find(lst: list[int], target: int) -> Optional[int]:
return target if target in lst else None

Hints are not enforced at runtime. Use mypy for static checking. Python 3.9+ allows list[int] instead of List[int].

11. dataclass

Show answer from dataclasses import dataclass

@dataclass
class Point:
x: float
y: float

p = Point(1.0, 2.0)
print(p) # Point(x=1.0, y=2.0)
p == Point(1.0, 2.0) # True (auto __eq__)

Auto-generates __init__, __repr__, __eq__. Use frozen=True for immutable.

12. NamedTuple

Show answer from typing import NamedTuple

class Point(NamedTuple):
x: float
y: float

p = Point(1.0, 2.0)
p.x # 1.0 (attribute access)
x, y = p # unpacking
p[0] # 1.0 (index access)

Immutable, lightweight, hashable. Great for simple records.

13. Generator expression

Show answer # Parentheses instead of brackets → lazy evaluation
total = sum(x**2 for x in range(1000000)) # no list in memory

lines = (line.strip() for line in open("big.log"))
for line in lines:
if "ERROR" in line:
print(line)

14. Custom context manager

Show answer from contextlib import contextmanager

@contextmanager
def timer():
import time
start = time.time()
yield # code inside 'with' runs here
print(f"elapsed: {time.time() - start:.2f}s")

with timer():
sum(range(10**7))
# elapsed: 0.23s

15. @property

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

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

c = Circle(5)
print(c.area) # 78.53975 — no parentheses

Makes a method look like an attribute. Add @area.setter for write access.

16. @staticmethod / @classmethod

Show answer class Date:
def __init__(self, y, m, d):
self.y, self.m, self.d = y, m, d

@classmethod
def from_str(cls, s):
return cls(*map(int, s.split("-")))

@staticmethod
def is_valid(s):
return len(s.split("-")) == 3

Date.from_str("2026-04-11") # creates Date
Date.is_valid("2026-04-11") # True

17. len and getitem

Show answer class Deck:
def __init__(self):
self.cards = list(range(52))
def __len__(self):
return len(self.cards)
def __getitem__(self, i):
return self.cards[i]

d = Deck()
len(d) # 52
d[0] # 0
d[-1] # 51
d[2:5] # [2, 3, 4] — slicing works too

18. enter and exit

Show answer class TempDir:
def __enter__(self):
import tempfile
self.path = tempfile.mkdtemp()
return self.path
def __exit__(self, exc_type, exc_val, exc_tb):
import shutil
shutil.rmtree(self.path)
return False # don't suppress exceptions

with TempDir() as d:
print(d) # /tmp/xyz — auto-cleaned up after

19. add, eq, lt (operator overloading)

Show answer class Vec:
def __init__(self, x, y):
self.x, self.y = x, y
def __add__(self, other):
return Vec(self.x + other.x, self.y + other.y)
def __eq__(self, other):
return self.x == other.x and self.y == other.y

Vec(1,2) + Vec(3,4) # Vec(4, 6)
Vec(1,2) == Vec(1,2) # True

20. super()

Show answer class Base:
def __init__(self, x):
self.x = x

class Child(Base):
def __init__(self, x, y):
super().__init__(x)
self.y = y

c = Child(1, 2)
print(c.x, c.y) # 1 2

Delegates to the next class in the MRO. Always use super() in __init__ for cooperative inheritance.

21. async / await

Show answer import asyncio

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

async def main():
results = await asyncio.gather(
fetch("a.com"),
fetch("b.com"),
)
print(results) # both finish in ~1s, not 2s

asyncio.run(main())

For I/O-bound concurrency. Not parallelism — use multiprocessing for CPU-bound work.

22. Exception hierarchy

Show answer # Common built-in exceptions:
# BaseException
# ├── KeyboardInterrupt
# ├── SystemExit
# └── Exception
# ├── ValueError
# ├── TypeError
# ├── KeyError
# ├── IndexError
# ├── FileNotFoundError
# ├── PermissionError
# ├── AttributeError
# └── RuntimeError

# Custom exceptions:
class AppError(Exception):
pass

class NotFoundError(AppError):
pass

23. Custom exceptions

Show answer class ValidationError(Exception):
def __init__(self, field, message):
self.field = field
super().__init__(f"{field}: {message}")

try:
raise ValidationError("email", "invalid format")
except ValidationError as e:
print(e) # email: invalid format
print(e.field) # email

24. Nested comprehension

Show answer matrix = [[1,2,3], [4,5,6], [7,8,9]]

# Flatten
flat = [x for row in matrix for x in row]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]

# Transpose
transpose = [[row[i] for row in matrix] for i in range(3)]
# [[1,4,7], [2,5,8], [3,6,9]]

25. collections.deque

Show answer from collections import deque

q = deque([1, 2, 3])
q.append(4) # right: [1, 2, 3, 4]
q.appendleft(0) # left: [0, 1, 2, 3, 4]
q.pop() # 4
q.popleft() # 0

# Fixed-size buffer (keeps last N)
buf = deque(maxlen=3)
for i in range(5):
buf.append(i)
print(buf) # deque([2, 3, 4])

26. itertools essentials

Show answer import itertools

# chain — flatten iterables
list(itertools.chain([1,2], [3,4])) # [1, 2, 3, 4]

# islice — lazy head/tail
list(itertools.islice(range(100), 5)) # [0, 1, 2, 3, 4]

# groupby (must be sorted first)
from itertools import groupby
data = sorted(["aa", "ab", "ba", "bb"], key=lambda x: x[0])
for k, g in groupby(data, key=lambda x: x[0]):
print(k, list(g)) # a ['aa','ab'], b ['ba','bb']

# product — nested loops
list(itertools.product("AB", "12")) # [('A','1'),('A','2'),('B','1'),('B','2')]

27. functools essentials

Show answer from functools import lru_cache, partial, reduce

# lru_cache — memoization
@lru_cache(maxsize=128)
def fib(n):
return n if n < 2 else fib(n-1) + fib(n-2)
fib(100) # instant

# partial — fix some arguments
from functools import partial
int_from_hex = partial(int, base=16)
int_from_hex("ff") # 255

# reduce — fold a sequence
reduce(lambda a, b: a * b, [1,2,3,4]) # 24

28. re (regular expressions)

Show answer import re

# Search
m = re.search(r"(\\d+)", "error on line 42")
m.group(1) # "42"

# Find all
re.findall(r"\\b\\w+@\\w+\\.\\w+\\b", text)

# Sub (replace)
re.sub(r"\\s+", " ", "too many spaces") # "too many spaces"

# Split
re.split(r"[;,]\\s*", "a, b; c") # ['a', 'b', 'c']

# Compile for reuse
pattern = re.compile(r"ERROR: (.+)")

29. logging

Show answer import logging

logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s",
)
log = logging.getLogger(__name__)

log.debug("details") # hidden (below INFO)
log.info("started") # shown
log.warning("slow") # shown
log.error("failed") # shown

Use logging, not print(), in production code.

30. argparse

Show answer import argparse

p = argparse.ArgumentParser(description="greet user")
p.add_argument("name")
p.add_argument("-n", "--count", type=int, default=1)
p.add_argument("-v", "--verbose", action="store_true")
args = p.parse_args()

for _ in range(args.count):
print(f"hello {args.name}")

# Usage: python greet.py alice -n 3 -v

31. threading

Show answer from concurrent.futures import ThreadPoolExecutor
import urllib.request

def fetch(url):
return urllib.request.urlopen(url).read()[:100]

urls = ["https://example.com"] * 5
with ThreadPoolExecutor(max_workers=5) as pool:
results = list(pool.map(fetch, urls))

Good for I/O-bound tasks. GIL prevents true CPU parallelism — use ProcessPoolExecutor for that.

32. multiprocessing

Show answer from concurrent.futures import ProcessPoolExecutor

def crunch(n):
return sum(i*i for i in range(n))

with ProcessPoolExecutor(max_workers=4) as pool:
results = list(pool.map(crunch, [10**6]*4))

Bypasses the GIL — true parallelism for CPU-bound work. Each worker is a separate process.

33. Abstract base class (ABC)

Show answer from abc import ABC, abstractmethod

class Shape(ABC):
@abstractmethod
def area(self) -> float:
...

class Circle(Shape):
def __init__(self, r):
self.r = r
def area(self):
return 3.14 * self.r ** 2

# Shape() → TypeError (can't instantiate)
Circle(5).area() # 78.5

34. str vs bytes

Show answer s = "hello" # str (unicode text)
b = b"hello" # bytes (raw bytes)

# Encode: str → bytes
b = s.encode("utf-8") # b'hello'

# Decode: bytes → str
s = b.decode("utf-8") # 'hello'

# You WILL hit this at network/file boundaries
# Always decode bytes to str as early as possible

35. for/else and while/else

Show answer for n in [2, 3, 5, 7]:
if n == 4:
print("found 4")
break
else:
print("4 not found") # ← runs because no break

The else block runs only if the loop completes without break. Useful for search patterns.

36. LEGB scope rule

Show answer x = "global"

def outer():
x = "enclosing"
def inner():
x = "local"
print(x) # local
inner()
print(x) # enclosing

outer()
print(x) # global

# Python searches: Local → Enclosing → Global → Built-in
# import builtins to see the B layer

37. Class variable vs instance variable

Show answer class Dog:
species = "canine" # class variable (shared)

def __init__(self, name):
self.name = name # instance variable (per-object)

a = Dog("rex")
b = Dog("fido")
Dog.species # "canine"
a.name # "rex"

# Gotcha: mutable class vars are shared!
class Bad:
items = [] # all instances share this list

🔴 Hard (2)

1. slots

Show answer class Point:
__slots__ = ("x", "y")
def __init__(self, x, y):
self.x = x
self.y = y

p = Point(1, 2)
# p.z = 3 → AttributeError (no dynamic attrs)

~40% less memory per instance. Use when creating millions of objects.

2. Protocol (structural typing)

Show answer from typing import Protocol

class Closable(Protocol):
def close(self) -> None: ...

def shutdown(obj: Closable) -> None:
obj.close()

# Any object with a .close() method works
# No inheritance needed — duck typing + type checking
shutdown(open("/dev/null")) # file has .close()