Python essentials
Data structures
nums = [1, 2, 3]
nums.append(4); nums.insert(0, 0); nums.pop(); nums.pop(0)
nums[-1]; nums[1:3]; nums[::-1] # last / slice / reversed
sorted(nums); nums.sort(reverse=True) # new sorted list / in place
3 in nums; nums.index(2); nums.count(1)
# comprehensions
[x**2 for x in range(10)]
[x for x in range(20) if x % 2 == 0]
[x for row in matrix for x in row] # flatten
{w: len(w) for w in words} # dict comprehension
{len(w) for w in words} # set comprehension
d = {"name": "adam"}
d.get("age", 25) # default instead of KeyError
d.pop("name"); d.items()
merged = d | {"team": "fcos"} # merge (3.9+)
from collections import defaultdict
counts = defaultdict(int); counts["a"] += 1 # missing key → 0
groups = defaultdict(list); groups["t"].append("x")
a = {1, 2, 3}; b = {2, 3, 4}
a | b; a & b; a - b; a ^ b # union/inter/diff/symdiff
list(dict.fromkeys([3, 1, 2, 1])) # dedup, preserve order → [3,1,2]
x, y = (3, 4) # tuple unpacking
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"]); Point(3, 4).x
Strings
s = "hello world"
s.strip(); s.split(","); ",".join(["a", "b"])
s.replace("world", "py"); s.startswith("he"); s.find("world")
name = "adam"
f"hello {name}" # use f-strings, not .format() or %
f"{3.14159:.2f}" # "3.14"
f"{1000000:,}" # "1,000,000"
f"{'hi':>10}" # right-align in width 10
Control flow
x = "even" if n % 2 == 0 else "odd" # ternary
for item in items: # for/else
if item == target: break
else:
print("not found") # runs if no break
if (n := len(items)) > 10: # walrus (3.8+)
print(f"too many: {n}")
match command: # match/case (3.10+)
case "quit": exit()
case "hello" | "hi": greet()
case _: print("unknown")
Functions
def greet(name, greeting="hello"):
return f"{greeting} {name}"
def log(*args, **kwargs): # positional tuple + keyword dict
...
add(*nums) # unpack list into args
add(**opts) # unpack dict into kwargs
double = lambda x: x * 2
sorted(items, key=lambda x: x["name"])
def process(items: list[str], count: int = 10) -> bool: # type hints
...
Classes
class Server:
count = 0 # class variable (shared)
def __init__(self, host, port=8080):
self.host = host; self.port = port
Server.count += 1
def url(self): return f"http://{self.host}:{self.port}"
def __repr__(self): return f"Server({self.host!r}, {self.port})"
def __eq__(self, o): return (self.host, self.port) == (o.host, o.port)
class SecureServer(Server): # inheritance
def __init__(self, host, port=443):
super().__init__(host, port)
def url(self): return f"https://{self.host}:{self.port}"
from dataclasses import dataclass # skip boilerplate
@dataclass
class Build:
stream: str
arch: str
version: str = "0.0.1" # auto __init__/__repr__/__eq__
Error handling
try:
result = risky()
except KeyError as e:
print(f"missing key: {e}")
except (TypeError, ValueError):
print("bad type or value")
except Exception as e:
print(f"unexpected: {e}"); raise # re-raise after logging
else:
print("no error") # runs if no exception
finally:
cleanup()
class BuildError(Exception): # custom exception
pass
File I/O, YAML, JSON
with open("data.txt") as f:
content = f.read()
lines = f.read().splitlines() # without trailing \n
with open("out.txt", "w") as f: # "a" to append
f.write("hello\n")
import yaml
config = yaml.safe_load(open("config.yaml"))
import json
data = json.loads('{"k": "v"}') # str → dict
text = json.dumps(data, indent=2) # dict → str
Common algorithm patterns
# two pointers (sorted)
def two_sum_sorted(nums, target):
lo, hi = 0, len(nums) - 1
while lo < hi:
t = nums[lo] + nums[hi]
if t == target: return [lo, hi]
lo, hi = (lo + 1, hi) if t < target else (lo, hi - 1)
# hash map for O(1) lookup
def two_sum(nums, target):
seen = {}
for i, n in enumerate(nums):
if target - n in seen: return [seen[target - n], i]
seen[n] = i
# sliding window
def max_sum(nums, k):
window = best = sum(nums[:k])
for i in range(k, len(nums)):
window += nums[i] - nums[i - k]
best = max(best, window)
return best
from collections import Counter, deque
Counter("banana") # {'a':3,'n':2,'b':1}
Counter(words).most_common(2)
# stack - balanced parens
def valid(s):
stack, pairs = [], {")": "(", "]": "[", "}": "{"}
for c in s:
if c in "([{": stack.append(c)
elif not stack or stack.pop() != pairs[c]: return False
return not stack
# BFS
def bfs(graph, start):
seen, q = {start}, deque([start])
while q:
node = q.popleft()
for nb in graph[node]:
if nb not in seen: seen.add(nb); q.append(nb)
return seen
# binary search (or use bisect.bisect_left)
def bsearch(nums, target):
lo, hi = 0, len(nums) - 1
while lo <= hi:
mid = (lo + hi) // 2
if nums[mid] == target: return mid
lo, hi = (mid + 1, hi) if nums[mid] < target else (lo, mid - 1)
return -1
Useful stdlib
for i, item in enumerate(items): ... # index + value
for a, b in zip(xs, ys): ... # parallel iteration
dict(zip(names, scores)) # build dict from two lists
any(x > 10 for x in nums); all(x > 0 for x in nums)
max(words, key=len); min(people, key=lambda p: p["age"])
from itertools import chain, combinations, permutations
list(combinations([1, 2, 3], 2)) # [(1,2),(1,3),(2,3)]
from functools import lru_cache
@lru_cache(maxsize=None)
def fib(n): return n if n < 2 else fib(n-1) + fib(n-2)
argparse
Once a script grows past one or two positional arguments, switch from
sys.argv to argparse - you get --help, defaults and flags for free.
import argparse
parser = argparse.ArgumentParser(description="Verify an RPM")
parser.add_argument("version", help="OCP version (e.g. 4.19)")
parser.add_argument("package", help="RPM name")
parser.add_argument("--no-pull", action="store_true", help="Skip image pull")
parser.add_argument("--rate-limit", type=float, default=2.0)
parser.add_argument("issue_key", nargs="?", default=None) # optional positional
args = parser.parse_args()
# args.version, args.no_pull (hyphens → underscores), ...
action="store_true" makes a boolean switch (True when the flag is given).
subprocess
import subprocess
r = subprocess.run(["ls", "-la"], capture_output=True, text=True)
r.stdout; r.returncode
subprocess.run(["make", "build"], check=True) # raises on non-zero exit
pathlib
from pathlib import Path
p = Path("/home/user/project")
p.exists(); p.is_dir()
p / "data" / "file.md" # join with /
p.name; p.parent; p.suffix; p.stem
list(p.glob("*.md")); list(p.rglob("*.py"))
Path("file.txt").read_text(); Path("file.txt").write_text("hi\n")
Path("out/sub").mkdir(parents=True, exist_ok=True)
Version-string parsing
Use int() so comparisons are numeric, not alphabetical
("9" > "12" is True - wrong; 9 > 12 is False - right), and guard
against missing parts:
parts = version.split(".") # "4.22.0-ec.5" → ['4','22','0-ec','5']
major = int(parts[0]) if len(parts) >= 1 else 0
minor = int(parts[1]) if len(parts) >= 2 else 0
Tuple comparison compares element by element - ideal for versions:
(4, 22) > (4, 12) is True; (4, 22) > (5, 1) is False. When
mapping OCP → RHEL, check major >= 5 separately so a future major version is
handled, rather than relying on minor >= 22 alone.
Complexity quick reference
Operation |
list |
dict/set |
|---|---|---|
index / key lookup |
O(1) |
O(1) |
search ( |
O(n) |
O(1) ← why you use sets |
append / add |
O(1) |
O(1) |
insert / pop at front |
O(n) |
|
sort |
O(n log n) |