Python Β· lesson 02/13
Python toolkit for data
This is the standard library you'll reach for in almost every data problem. Learn these idioms well enough to type them without thinking.
1. Fast input / output
import sys
def main():
data = sys.stdin.buffer.read().split() # all tokens as bytes; fastest
# or: lines = sys.stdin.read().splitlines() # when lines contain spaces
n = int(data[0])
nums = list(map(int, data[1:1 + n]))
out = []
for x in nums:
out.append(str(x * 2))
sys.stdout.write("\n".join(out) + "\n") # ONE write at the end
main() # wrapping in a function makes local variable lookups faster
input() plus print() for each of 10βΆ lines can take seconds. Read everything at once and write once.
If lines may contain spaces, use splitlines(), not split().
2. collections: the data engineer's best friends
from collections import Counter, defaultdict, deque, OrderedDict
c = Counter(["a", "b", "a", "c", "a"])
c.most_common(2) # [('a', 3), ('b', 1)] (ties keep first-seen order, so sort explicitly if needed)
c["zzz"] # 0, never KeyError
groups = defaultdict(list) # group-by in one line
for user, amount in rows:
groups[user].append(amount)
totals = defaultdict(float)
for user, amount in rows:
totals[user] += amount
window = deque(maxlen=3) # sliding window of the last 3 items
window.append(x) # O(1) at both ends; list.pop(0) is O(n)!
3. Sorting like a pro
rows.sort(key=lambda r: (-r["total"], r["name"])) # total DESC, name ASC
sorted(ids, key=int) # numeric strings
sorted(words, key=str.lower) # case-insensitive
# Sort is STABLE: sort by the secondary key first, then by the primary key
rows.sort(key=lambda r: r["name"]); rows.sort(key=lambda r: r["dept"])
# can't negate a string? sort twice or use reverse=True on that pass
from operator import itemgetter
rows.sort(key=itemgetter("dept", "name"))
4. itertools & heapq
from itertools import groupby, chain, islice, accumulate, combinations, pairwise
import heapq
# groupby only groups CONSECUTIVE equal keys, so sort first!
data.sort(key=lambda r: r["user"])
for user, grp in groupby(data, key=lambda r: r["user"]):
grp = list(grp)
list(accumulate([3, 1, 4])) # running sum [3, 4, 8]
list(pairwise([1, 5, 6])) # [(1, 5), (5, 6)] (Python 3.10+): consecutive diffs
heapq.nlargest(3, items, key=lambda x: x[1]) # top-K in O(n log k)
heapq.merge(sorted_a, sorted_b) # merge sorted streams lazily (external sort!)
5. Dates and times
from datetime import datetime, date, timedelta, timezone
t = datetime.strptime("2026-10-23 16:00:05", "%Y-%m-%d %H:%M:%S")
t = datetime.fromisoformat("2026-10-23T16:00:05+03:30") # handles offsets and 'Z' (3.11+)
(t2 - t1).total_seconds() # use total_seconds(), NOT .seconds (that ignores days!)
d.isoformat(); t.strftime("%Y-%m") # month bucket
t.date(); t.weekday() # Monday = 0
t.astimezone(timezone.utc)
date(2026, 10, 23) + timedelta(days=7)
# ISO-8601 strings in the SAME format and timezone sort correctly as plain strings, and that's fast
timedelta.seconds is only the seconds part (0β86399). A two-day gap has
.seconds == 0. Always use .total_seconds().
6. JSON, CSV, regex
import json, csv, re, io
obj = json.loads(line) # one JSON per line = "JSON Lines"
obj.get("user", {}).get("id") # safe nested access
json.dumps(obj, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
reader = csv.DictReader(io.StringIO(text)) # handles quoted commas: "Tehran, Iran"
for row in reader: ... # values are ALWAYS strings, so convert!
w = csv.writer(sys.stdout); w.writerow(["id", "name"])
LOG = re.compile(r'(?P<ip>\S+) .*?\[(?P<ts>[^\]]+)\] "(?P<method>\w+) (?P<path>\S+)[^"]*" (?P<status>\d{3})')
m = LOG.search(line)
if m: m["path"], int(m["status"])
Never parse CSV with line.split(",") unless the statement guarantees there are no quoted fields.
csv handles quoting, embedded commas and escaped quotes for you.
7. Numbers and money
from decimal import Decimal, ROUND_HALF_UP
round(2.5), round(3.5) # 2, 4 (banker's rounding!)
f"{2.675:.2f}" # '2.67' (binary float)
Decimal("2.675").quantize(Decimal("0.01"), rounding=ROUND_HALF_UP) # 2.68
7 // 2, -7 // 2 # 3, -4 (floor, not truncation)
float("nan") == float("nan") # False; use math.isnan
f"{1234567.891:,.2f}" # '1,234,567.89'
8. dataclasses & typing (template-style problems)
from dataclasses import dataclass, field
from typing import Iterator
@dataclass(frozen=True)
class Event:
user_id: str
ts: int
kind: str = "view"
def read_events(lines) -> Iterator[Event]: # generators = streaming, constant memory
for line in lines:
u, ts, k = line.rstrip("\n").split("\t")
yield Event(u, int(ts), k)
9. Input templates for the usual stdin shapes
import sys
lines = sys.stdin.read().splitlines()
# (a) n, then n records
n = int(lines[0]); records = [l.split(",") for l in lines[1:1 + n]]
# (b) records until EOF (skip blank lines, which often hide at the end)
records = [l for l in lines if l.strip()]
# (c) header line + CSV body
import csv, io
rows = list(csv.DictReader(io.StringIO("\n".join(lines))))
# (d) T test cases, each "n" followed by n numbers on one line
it = iter(sys.stdin.read().split())
for _ in range(int(next(it))):
n = int(next(it)); arr = [int(next(it)) for _ in range(n)]
# (e) first line "n m", then an n Γ m grid
n, m = map(int, lines[0].split()); grid = [lines[1 + i].split() for i in range(n)]
The token-iterator pattern (d) ignores how the input is split into lines. It's the safest choice when the statement is vague about line breaks.
10. Gotchas that cost points
| Code | Surprise | Fix |
|---|---|---|
def f(x, acc=[]) | The default list is shared between calls | acc=None, then acc = acc or [] |
isinstance(True, int) | True: bool is a subclass of int | Check bool first |
json.loads('{"1": 2}') | Keys are always str | {int(k): v for k, v in d.items()} |
"a,b,".split(",") | ['a', 'b', ''], with an empty last field | Filter, or use csv |
"a b".split(" ") | ['a', '', 'b'] | split() with no argument splits on any whitespace run |
sorted([3, None, 1]) | TypeError | key=lambda x: (x is None, x or 0) puts None last |
[[0] * m] * n | n references to the same row | [[0] * m for _ in range(n)] |
fns = [lambda: i for i in range(3)] | All return 2 (late binding) | lambda i=i: i |
| Deep recursion (DFS on 10β΅ nodes) | RecursionError (default limit 1000) | Iterative stack, or sys.setrecursionlimit(10**6) |
0.1 + 0.2 == 0.3 | False | math.isclose, or Decimal for money |
dict modified while iterating | RuntimeError | Iterate over list(d) |
s += piece in a 10βΆ loop | Can be quadratic | Collect in a list, then "".join(parts) |
11. Output formatting cookbook
f"{x:.2f}" # 3.14159 β '3.14' (always 2 decimals)
f"{x:g}" # 3.0 β '3', 2.5 β '2.5' (drop trailing zeros)
f"{n:,}" # 1234567 β '1,234,567'
f"{p:.1%}" # 0.256 β '25.6%'
f"{n:05d}" # 42 β '00042'
f"{s:<10}|" # left-align in 10 chars ('>' right, '^' centre)
print(*row, sep=",") # join any values without str() calls
print(json.dumps(obj, sort_keys=True)) # deterministic JSON output
str(True), json.dumps(True) # 'True' vs 'true': read which one the statement wants