Data Structures, Comprehensions, Decorators, Async/Await, OOP, Stdlib — Python mastery.
# ── Numeric Types ──
x: int = 42 # arbitrary precision integer
y: float = 3.14 # double-precision float (IEEE 754)
z: complex = 2 + 3j # complex number (real + imaginary)
# Underscores for readability (PEP 515)
big_num: int = 1_000_000
bytes_size: int = 0xFF # hex literal
octal: int = 0o755 # octal literal
binary: int = 0b1010 # binary literal
# ── String ──
name: str = "Alice"
multiline: str = """\
Line 1
Line 2
Line 3
"""
raw_str: str = r"C:\Users\path" # raw string (no escaping)
f_string: str = f"Hello, {name}! Age: {x}"
# ── Boolean & None ──
flag: bool = True
nothing = None # NoneType singleton# ── Type Checking ──
# isinstance() — preferred (supports inheritance)
isinstance(42, int) # True
isinstance(True, int) # True (bool is subclass of int)
isinstance(3.14, (int, float)) # True (tuple of types)
# type() — exact type match
type(42) is int # True
type(True) is int # False
type(True) is bool # True
# ── Type Hints (Python 3.12+) ──
# PEP 695 — type parameter syntax (Python 3.12+)
def first[T](items: list[T]) -> T | None:
return items[0] if items else None
# Generic classes with type parameters
class Stack[T]:
def __init__(self) -> None:
self._items: list[T] = []
def push(self, item: T) -> None:
self._items.append(item)
def pop(self) -> T:
return self._items.pop()
def __bool__(self) -> bool:
return bool(self._items)
# Type aliases (Python 3.12+)
type Vector = list[float]
type Matrix = list[Vector]
type JSON = dict[str, "JSON | list[JSON] | str | int | float | bool | None"]
# Multiple type params with bounds
def pair[T: (int, float), U](a: T, b: U) -> tuple[T, U]:
return (a, b)# ── f-strings & Format Specifiers ──
pi = 3.14159265
# Basic formatting
f"Pi = {pi:.2f}" # "Pi = 3.14"
f"Pi = {pi:.6f}" # "Pi = 3.141593"
f"Hex = {255:#x}" # "Hex = 0xff"
f"Bin = {255:#b}" # "Bin = 0b11111111"
# Width and alignment
f"{'left':<20}" # left-align, width 20
f"{'right':>20}" # right-align, width 20
f"{'center':^20}" # center-align, width 20
f"{1000000:,}" # "1,000,000" (thousands sep)
f"{0.85:.1%}" # "85.0%" (percentage)
# Debugging (Python 3.12+)
user = {"name": "Alice", "age": 30}
f"{user=}" # "user={'name': 'Alice', 'age': 30}"
f"{pi=:.2f}" # "pi=3.14"
# Number formatting
f"{42:08d}" # "00000042" (zero-padded)
f"{3.14:+.2f}" # "+3.14" (show sign)
f"{3.14: .2f}" # " 3.14" (space for positive)
f"{1_000_000:e}" # "1.000000e+06"
# String templates (alternative)
from string import Template
t = Template("Hello, $name! You have $count messages.")
t.substitute(name="Bob", count=5)| Syntax | Meaning | Version |
|---|---|---|
| int, float, str, bool | Built-in types | 3.0+ |
| list[int] | List of ints | 3.9+ |
| dict[str, int] | Dict mapping | 3.9+ |
| tuple[int, ...] | Tuple of N ints | 3.9+ |
| X | None | Optional (Union) | 3.10+ |
| type X = ... | Type alias | 3.12+ |
| def f[T](): | Type parameter | 3.12+ |
| T: int | Type bound | 3.12+ |
| X & Y | Intersection type | 3.12+ |
isinstance() instead of type() is ... for type checking — it respects inheritance hierarchies and supports tuple-based type checks for multiple types.# ── Lists (mutable, ordered, heterogeneous) ──
fruits: list[str] = ["apple", "banana", "cherry"]
fruits.append("date") # add to end
fruits.insert(1, "avocado") # insert at index
fruits.extend(["elderberry"]) # extend with iterable
fruits.pop(0) # remove by index, return value
fruits.remove("banana") # remove first occurrence
fruits.sort() # in-place sort
fruits.reverse() # in-place reverse
fruits[1:3] = ["x", "y"] # slice assignment
# List operations
squares = [x**2 for x in range(10)]
matrix = [[i*3+j for j in range(3)] for i in range(3)]
copied = fruits.copy() # shallow copy
copied2 = fruits[:] # shallow copy (idiomatic)
nested_copy = [row[:] for row in matrix] # 2D deep copy
# ── Tuples (immutable, ordered, hashable) ──
point: tuple[int, int] = (3, 4)
rgb: tuple[int, int, int] = (255, 128, 0)
singleton: tuple[int] = (42,) # comma for single-element
empty: tuple[()] = ()
# Tuple unpacking
x, y = point
r, g, b = rgb
first, *rest = [1, 2, 3, 4] # first=1, rest=[2,3,4]
*head, last = [1, 2, 3, 4] # head=[1,2,3], last=4
a, b, *c = (1, 2, 3, 4, 5) # a=1, b=2, c=[3,4,5]
# Named tuple (memory efficient, self-documenting)
from collections import namedtuple
Point = namedtuple("Point", ["x", "y"])
p = Point(3, 4)
print(p.x, p.y) # 3 4
print(p[0], p[1]) # 3 4 (index access)
print(p._asdict()) # {'x': 3, 'y': 4}# ── Sets (mutable, unordered, unique elements) ──
s: set[int] = {1, 2, 3, 3, 4} # {1, 2, 3, 4}
s.add(5)
s.discard(4) # safe remove (no error)
s.remove(3) # error if not found
s.update({6, 7}) # add multiple
subset = {1, 2} <= s # issubset
superset = s >= {1, 2} # issuperset
diff = s - {1, 2} # difference
inter = s & {3, 4, 5} # intersection
union = s | {8, 9} # union
# Frozenset (immutable, hashable — usable as dict key)
fs = frozenset([1, 2, 3])
# ── Dictionaries (mutable, key-value pairs) ──
d: dict[str, int] = {"a": 1, "b": 2}
# Dict operations
d["c"] = 3 # set / create
d.get("z", 0) # get with default
d.setdefault("c", 99) # set if not exists
d.update({"d": 4, "e": 5}) # merge dicts
del d["a"] # delete key
popped = d.pop("b", None) # pop with default
# Dict views
keys = d.keys() # dict_keys (set-like in 3.9+)
vals = d.values() # dict_values
items = d.items() # dict_items (set-like)
# Dict unpacking & merging (Python 3.9+)
merged = d | {"f": 6} # merge operator
d |= {"g": 7} # in-place updatefrom collections import defaultdict, OrderedDict, Counter, deque, ChainMap
# ── defaultdict — auto-initializes missing keys ──
dd: dict[str, list[int]] = defaultdict(list)
dd["fruits"].append("apple") # key auto-created with []
dd["fruits"].append("banana")
print(dd) # {'fruits': ['apple', 'banana']}
# Grouping with defaultdict
from itertools import groupby
words = ["apple", "banana", "avocado", "blueberry", "cherry"]
by_letter: dict[str, list[str]] = defaultdict(list)
for w in words:
by_letter[w[0]].append(w)
# {'a': ['apple', 'avocado'], 'b': ['banana', 'blueberry'], 'c': ['cherry']}
# ── OrderedDict — preserves insertion order (dict does this natively in 3.7+) ──
od = OrderedDict([("a", 1), ("b", 2), ("c", 3)])
od.move_to_end("a") # move key to end
od.move_to_end("b", last=False) # move key to front
od.popitem(last=True) # pop last item (LIFO)
# ── Counter — count hashable objects ──
text = "abracadabra"
counts = Counter(text) # {'a': 5, 'b': 2, 'r': 2, 'c': 1, 'd': 1}
counts.most_common(3) # [('a', 5), ('b', 2), ('r', 2)]
counts.update("extra") # add more counts
counts.subtract("abc") # subtract counts
merged = counts + Counter("xyz") # add two counters
# ── deque — double-ended queue (O(1) append/pop on both ends) ──
dq: deque[str] = deque(["a", "b", "c"], maxlen=5)
dq.appendleft("z") # add to front
dq.append("d") # add to end
dq.popleft() # remove from front
dq.pop() # remove from end
dq.rotate(1) # rotate right 1
dq.rotate(-1) # rotate left 1
# ── ChainMap — group multiple dicts as a single view ──
defaults = {"theme": "dark", "lang": "en"}
user_config = {"lang": "fr", "font": "monospace"}
env = {"debug": True}
combined = ChainMap(user_config, defaults, env)
combined["lang"] # "fr" (user_config wins)
combined["theme"] # "dark" (fallback)# ── Dataclasses (Python 3.10+ / 3.12+ improvements) ──
from dataclasses import dataclass, field, FrozenInstanceError
@dataclass(frozen=True, slots=True, kw_only=True)
class Point:
x: float
y: float
label: str = "origin"
_cache: dict = field(default_factory=dict, repr=False)
def distance_to(self, other: "Point") -> float:
return ((self.x - other.x)**2 + (self.y - other.y)**2) ** 0.5
p1 = Point(x=3.0, y=4.0)
p2 = Point(x=0.0, y=0.0, label="origin")
print(p1.distance_to(p2)) # 5.0
# Pattern matching with dataclasses (Python 3.10+)
def describe(point: Point) -> str:
match point:
case Point(x=0, y=0):
return "Origin"
case Point(x=x, y=0):
return f"On x-axis at {x}"
case Point(x=0, y=y):
return f"On y-axis at {y}"
case Point(label=name):
return f"Point named {name}"
case _:
return "Some point"
# ── Enums ──
from enum import Enum, IntEnum, Flag, StrEnum, auto
class Status(StrEnum): # StrEnum (Python 3.11+)
PENDING = auto()
ACTIVE = "active"
CLOSED = auto()
print(Status.ACTIVE.value) # "active"
print(Status.ACTIVE) # Status.ACTIVE
class Permission(Flag):
READ = auto()
WRITE = auto()
EXECUTE = auto()
ALL = READ | WRITE | EXECUTE
# Flag operations
perm = Permission.READ | Permission.WRITE
perm & Permission.READ # Permission.READ (True)
Permission.READ in perm # True| Structure | Mutable | Ordered | Hashable | Syntax |
|---|---|---|---|---|
| list | Yes | Yes | No | [1, 2, 3] |
| tuple | No | Yes | Yes* | (1, 2, 3) |
| set | Yes | No** | No | {1, 2, 3} |
| frozenset | No | No** | Yes | frozenset({1,2}) |
| dict | Yes | Yes*** | No | {"a": 1} |
*Tuples are hashable only if all elements are hashable. **Sets are insertion-ordered in CPython 3.7+ but not guaranteed. ***Dicts preserve insertion order since Python 3.7 (official in 3.8).
| Operation | list | set | dict |
|---|---|---|---|
| Access by index/key | O(1) | N/A | O(1) |
| Append / add | O(1)* | O(1) | O(1) |
| Insert | O(n) | N/A | N/A |
| Delete by index/key | O(n) | O(1) | O(1) |
| Search (in / x in) | O(n) | O(1) | O(1) |
| Sort | O(n log n) | N/A | N/A |
*list.append amortized O(1), worst-case O(n) on resize.
x in collection), always use a set instead of a list — O(1) vs O(n) lookup.# ── List Comprehensions ──
# Basic: [expression for item in iterable]
squares = [x**2 for x in range(10)]
# [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
# With condition: [expr for item in iterable if condition]
evens = [x for x in range(20) if x % 2 == 0]
# [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
# Nested comprehension
pairs = [(x, y) for x in range(3) for y in range(3) if x != y]
# [(0,1), (0,2), (1,0), (1,2), (2,0), (2,1)]
# Flattening a matrix
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flat = [num for row in matrix for num in row]
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
# Expression with function calls
words = ["Hello", "World", "Python"]
upper = [w.upper() for w in words]
# ['HELLO', 'WORLD', 'PYTHON']
# Ternary in comprehension
labels = ["even" if x % 2 == 0 else "odd" for x in range(5)]
# ['even', 'odd', 'even', 'odd', 'even']# ── Dict Comprehensions ──
# {key_expr: value_expr for item in iterable}
word_lengths = {word: len(word) for word in ["hi", "hello", "world"]}
# {'hi': 2, 'hello': 5, 'world': 5}
# Swap keys and values
original = {"a": 1, "b": 2, "c": 3}
swapped = {v: k for k, v in original.items()}
# {1: 'a', 2: 'b', 3: 'c'}
# Conditional dict comprehension
scores = {"alice": 85, "bob": 92, "charlie": 78, "diana": 95}
passed = {name: score for name, score in scores.items() if score >= 80}
# {'alice': 85, 'bob': 92, 'diana': 95}
# Transforming values
prices = {"apple": 1.2, "banana": 0.8, "cherry": 2.5}
with_tax = {item: round(price * 1.1, 2) for item, price in prices.items()}
# ── Set Comprehensions ──
# {expression for item in iterable}
sentence = "the quick brown fox jumps over the lazy dog"
vowels = {char for char in sentence if char in "aeiou"}
# {'e', 'o', 'u', 'a', 'i'}
# Unique lengths
words = ["cat", "dog", "bird", "fish", "ant"]
lengths = {len(w) for w in words}
# {3, 4}
# Set from nested structure
data = [("a", 1), ("b", 2), ("a", 3), ("c", 1)]
keys = {key for key, _ in data}
# {'a', 'b', 'c'}# ── Generator Expressions ──
# Lazy evaluation — produces items one at a time
# Syntax: (expression for item in iterable) — use with sum(), max(), etc.
# Memory efficient: does NOT create an intermediate list
total = sum(x**2 for x in range(1_000_001))
max_val = max(len(word) for word in open("words.txt"))
# Generator pipelines
data = range(1, 100)
result = (
x**2 # square
for x in data
if x % 3 == 0 # divisible by 3
)
# Materialize when needed
squares_list = list(x**2 for x in range(10))
# ── Walrus Operator := (Assignment Expressions, Python 3.8+) ──
# Assign within expressions — reduces duplication
import re
# Traditional
match = re.search(r"\d+", "age: 42")
if match:
print(match.group()) # "42"
# With walrus
if (m := re.search(r"\d+", "age: 42")):
print(m.group()) # "42"
# Filtering with computation (avoid double work)
results = [y for x in range(20) if (y := x**2) > 50]
# [64, 81, 100, 121, ...]
# While loop with inline update
while chunk := file.read(8192):
process(chunk)
# Useful in list comprehension for complex conditions
valid = [
item.transform()
for item in items
if (transformed := item.transform()) is not None
and transformed.size > 0
]# ── Nested Comprehensions ──
# Matrix transpose
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
transposed = [[row[i] for row in matrix] for i in range(3)]
# [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
# Cartesian product with condition
colors = ["red", "blue"]
sizes = ["S", "M", "L"]
in_stock = [(c, s) for c in colors for s in sizes if not (c == "red" and s == "L")]
# [('red', 'S'), ('red', 'M'), ('blue', 'S'), ('blue', 'M'), ('blue', 'L')]
# Directory tree flattening
tree = {
"src": ["main.py", "utils.py"],
"tests": ["test_main.py", "test_utils.py"],
"docs": ["README.md"],
}
all_files = [f"{dir}/{file}" for dir, files in tree.items() for file in files]
# ['src/main.py', 'src/utils.py', 'tests/test_main.py', ...]
# ── Comprehension Performance Tips ──
# FAST: list comprehension
%timeit [x**2 for x in range(1000)]
# ~30 μs
# SLOWER: map with lambda
%timeit list(map(lambda x: x**2, range(1000)))
# ~55 μs
# SLOWEST: for loop with append
result = []
for x in range(1000):
result.append(x**2)
# ~60 μs
# Use generator expression when only consuming once
total = sum(x**2 for x in range(1_000_000)) # no intermediate list| Type | Syntax | Result | Lazy? |
|---|---|---|---|
| List comp | [x for x in ...] | list | No |
| Dict comp | {k: v for ...} | dict | No |
| Set comp | {x for x in ...} | set | No |
| Generator | (x for x in ...) | generator | Yes |
# ── Function Definitions ──
def greet(name: str, greeting: str = "Hello") -> str:
"""Greet someone with a configurable greeting."""
return f"{greeting}, {name}!"
greet("Alice") # "Hello, Alice!"
greet("Bob", "Hey") # "Hey, Bob!"
# ── *args and **kwargs ──
def variadic(*args: str, **kwargs: int) -> None:
print(args) # tuple of positional
print(kwargs) # dict of keyword
variadic("a", "b", "c", x=1, y=2)
# ('a', 'b', 'c')
# {'x': 1, 'y': 2}
# ── Keyword-Only Arguments (Python 3.0+) ──
def search(query: str, *, limit: int = 10, offset: int = 0) -> list:
"""query is positional-or-keyword, limit/offset are keyword-only."""
return results[offset:offset + limit]
search("python", limit=50) # OK
search("python", 50) # TypeError! 50 must be keyword
# ── Positional-Only Arguments (Python 3.8+) ──
def func(a: int, b: int, /, c: int, d: int, *, e: int) -> str:
"""
a, b: positional-only
c, d: positional-or-keyword
e: keyword-only
"""
return f"{a} {b} {c} {d} {e}"
func(1, 2, 3, d=4, e=5) # OK
func(1, 2, c=3, d=4, e=5) # OK
func(a=1, b=2, c=3, d=4, e=5) # TypeError! a, b are positional-only
# ── Lambda (inline anonymous function) ──
square = lambda x: x**2
add = lambda x, y: x + y
sort_by_second = lambda pair: pair[1]
pairs = [(1, "b"), (3, "a"), (2, "c")]
sorted(pairs, key=sort_by_second) # [(3, 'a'), (1, 'b'), (2, 'c')]# ── Closures ──
# A closure remembers the enclosing scope's variables
def make_multiplier(factor: int):
"""Returns a function that multiplies by factor."""
def multiplier(x: int) -> int:
return x * factor
return multiplier
double = make_multiplier(2)
triple = make_multiplier(3)
double(5) # 10
triple(5) # 15
# Closure with state
def make_counter(start: int = 0):
count = start
def increment() -> int:
nonlocal count # modify enclosing variable
count += 1
return count
return increment
counter = make_counter(10)
counter() # 11
counter() # 12
# Closure as a config factory
def make_formatter(fmt: str):
def format_value(value) -> str:
return fmt.format(value=value)
return format_value
currency_fmt = make_formatter("$" + "{value:.2f}")
pct_fmt = make_formatter("{value:.1f}%")
currency_fmt(42.5) # "$42.50"
pct_fmt(0.856) # "85.6%"import functools
import time
import logging
# ── Custom Decorator (function-based) ──
def timer(func):
"""Measure execution time of a function."""
@functools.wraps(func) # preserves func.__name__, __doc__
def wrapper(*args, **kwargs):
start = time.perf_counter()
result = func(*args, **kwargs)
elapsed = time.perf_counter() - start
logging.info(f"{func.__name__} took {elapsed:.4f}s")
return result
return wrapper
@timer
def slow_function(n: int) -> int:
"""Compute sum of squares (intentionally slow example)."""
return sum(i**2 for i in range(n))
# ── Decorator with Arguments ──
def retry(max_attempts: int = 3, delay: float = 1.0, exc_type=Exception):
"""Retry a function on failure."""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
for attempt in range(1, max_attempts + 1):
try:
return func(*args, **kwargs)
except exc_type as e:
if attempt == max_attempts:
raise
logging.warning(
f"Attempt {attempt}/{max_attempts} failed: {e}"
)
time.sleep(delay * attempt) # exponential backoff
return None
return wrapper
return decorator
@retry(max_attempts=5, delay=0.5, exc_type=ConnectionError)
def fetch_data(url: str) -> dict:
"""Fetch data with automatic retries."""
import urllib.request
with urllib.request.urlopen(url) as resp:
return json.loads(resp.read())
# ── Class-based Decorator ──
class CountCalls:
"""Count how many times a function is called."""
def __init__(self, func):
self.func = func
self.count = 0
functools.update_wrapper(self, func)
def __call__(self, *args, **kwargs):
self.count += 1
print(f"Call #{self.count} of {self.func.__name__}")
return self.func(*args, **kwargs)
@CountCalls
def say_hello(name: str) -> str:
return f"Hello, {name}!"import functools
from functools import partial, lru_cache, wraps, reduce, singledispatch
# ── partial — pre-fill arguments ──
def power(base: float, exp: float) -> float:
return base ** exp
square = partial(power, exp=2)
cube = partial(power, exp=3)
square(5) # 25.0
cube(5) # 125.0
# Practical: pre-filling a logging call
debug = partial(logging.log, level=logging.DEBUG)
debug("Starting process")
# ── lru_cache — memoization ──
@lru_cache(maxsize=128)
def fibonacci(n: int) -> int:
if n < 2:
return n
return fibonacci(n - 1) + fibonacci(n - 2)
fibonacci(100) # Fast! O(n) with cache
fibonacci.cache_info() # CacheStatistics(hits=..., misses=...)
fibonacci.cache_clear() # Reset cache
# Typed version (Python 3.9+)
from functools import cache # unbounded LRU cache (shortcut)
@cache
def factorial(n: int) -> int:
return 1 if n <= 1 else n * factorial(n - 1)
# ── singledispatch — overloaded functions ──
@singledispatch
def process(value):
raise NotImplementedError(f"Cannot process {type(value)}")
@process.register(int)
def _(value: int) -> str:
return f"Integer: {value}"
@process.register(list)
def _(value: list) -> str:
return f"List of {len(value)} items"
@process.register(str)
def _(value: str) -> str:
return f"String: {value!r}"
process(42) # "Integer: 42"
process([1, 2, 3]) # "List of 3 items"
process("hello") # "String: 'hello'"# ── @staticmethod, @classmethod, @property ──
class Circle:
__slots__ = ("_radius",) # prevents dynamic attributes
def __init__(self, radius: float):
self._radius = radius
@property
def radius(self) -> float:
"""Getter — access like an attribute."""
return self._radius
@radius.setter
def radius(self, value: float) -> None:
"""Setter — validate before setting."""
if value <= 0:
raise ValueError("Radius must be positive")
self._radius = value
@property
def area(self) -> float:
"""Computed property (read-only)."""
import math
return math.pi * self._radius ** 2
@staticmethod
def unit_circle() -> "Circle":
"""Factory method — doesn't need self or cls."""
return Circle(1.0)
@classmethod
def from_diameter(cls, diameter: float) -> "Circle":
"""Alternative constructor — receives the class."""
return cls(diameter / 2)
def __repr__(self) -> str:
return f"Circle(radius={self._radius:.2f})"
# Usage
c = Circle.from_diameter(10) # Circle(5.0)
print(c.radius) # 5.0
print(c.area) # 78.53981633974483
c.radius = 7
c.radius = -1 # ValueError!| Decorator | Purpose | Receives |
|---|---|---|
| @staticmethod | Utility method, no self/cls | Only explicit args |
| @classmethod | Factory / alt constructor | cls as first arg |
| @property | Computed / protected attrs | self as first arg |
| @functools.wraps | Preserve metadata | N/A (wraps decorator) |
| @functools.cache | Memoization | N/A |
| @functools.lru_cache | Bounded memoization | N/A |
| @functools.singledispatch | Type-based overload | N/A |
| @dataclass | Auto __init__/__repr__ | N/A |
def f(pos_only, /, pos_or_kw, *, kw_only):@functools.wraps(func) in your decorators to preserve the wrapped function's __name__, __doc__, and __module__. Without it, debugging becomes painful.# ── Classes & Inheritance ──
class Animal:
"""Base class for all animals."""
species_count: int = 0 # class variable (shared)
def __init__(self, name: str, age: int) -> None:
self.name = name # instance variable
self._age = age # protected (convention)
self.__secret = "hidden" # name-mangled to _Animal__secret
Animal.species_count += 1
def speak(self) -> str:
raise NotImplementedError
def __str__(self) -> str:
return f"{self.__class__.__name__}({self.name})"
def __repr__(self) -> str:
return f"{self.__class__.__name__}(name={self.name!r}, age={self._age})"
class Dog(Animal):
def __init__(self, name: str, age: int, breed: str) -> None:
super().__init__(name, age) # call parent __init__
self.breed = breed
def speak(self) -> str:
return f"{self.name} says Woof!"
# Overriding with extension
def __repr__(self) -> str:
base = super().__repr__()
return f"{base}, breed={self.breed!r}"
# ── Multiple Inheritance & MRO ──
class Flyable:
def fly(self) -> str:
return f"{self.__class__.__name__} takes off!"
class Swimmer:
def swim(self) -> str:
return f"{self.__class__.__name__} swims!"
class Duck(Animal, Flyable, Swimmer):
def speak(self) -> str:
return "Quack!"
# MRO — Method Resolution Order (C3 linearization)
print(Duck.__mro__)
# (<class 'Duck'>, <class 'Animal'>, <class 'Flyable'>,
# <class 'Swimmer'>, <class 'object'>)# ── Abstract Base Classes (ABC) ──
from abc import ABC, abstractmethod, abstractproperty
class Shape(ABC):
"""Abstract base class — cannot be instantiated directly."""
@abstractmethod
def area(self) -> float:
"""Calculate the area of the shape."""
...
@abstractmethod
def perimeter(self) -> float:
"""Calculate the perimeter of the shape."""
...
@abstractproperty
def name(self) -> str:
"""Human-readable shape name."""
...
def describe(self) -> str:
"""Concrete method available to all subclasses."""
return f"{self.name}: area={self.area():.2f}, perimeter={self.perimeter():.2f}"
class Rectangle(Shape):
def __init__(self, width: float, height: float) -> None:
self.width = width
self.height = height
@property
def name(self) -> str:
return "Rectangle"
def area(self) -> float:
return self.width * self.height
def perimeter(self) -> float:
return 2 * (self.width + self.height)
class Circle(Shape):
import math
def __init__(self, radius: float) -> None:
self.radius = radius
@property
def name(self) -> str:
return "Circle"
def area(self) -> float:
import math
return math.pi * self.radius ** 2
def perimeter(self) -> float:
import math
return 2 * math.pi * self.radius
# Rectangle("hello") # TypeError: missing required positional arguments
# Shape() # TypeError: Can't instantiate abstract class# ── Dunder (Magic) Methods ──
class Vector:
"""A 2D vector with full operator support."""
__slots__ = ("x", "y")
def __init__(self, x: float = 0.0, y: float = 0.0):
self.x = x
self.y = y
# Representation
def __repr__(self) -> str:
return f"Vector({self.x}, {self.y})"
def __str__(self) -> str:
return f"({self.x}, {self.y})"
# Comparison
def __eq__(self, other: object) -> bool:
if not isinstance(other, Vector):
return NotImplemented
return self.x == other.x and self.y == other.y
def __hash__(self) -> int:
return hash((self.x, self.y))
def __lt__(self, other: "Vector") -> bool:
return (self.x**2 + self.y**2) < (other.x**2 + other.y**2)
# Arithmetic
def __add__(self, other: "Vector") -> "Vector":
return Vector(self.x + other.x, self.y + other.y)
def __sub__(self, other: "Vector") -> "Vector":
return Vector(self.x - other.x, self.y - other.y)
def __mul__(self, scalar: float) -> "Vector":
return Vector(self.x * scalar, self.y * scalar)
def __rmul__(self, scalar: float) -> "Vector":
return self.__mul__(scalar)
def __neg__(self) -> "Vector":
return Vector(-self.x, -self.y)
def __abs__(self) -> float:
return (self.x**2 + self.y**2) ** 0.5
# Length
def __len__(self) -> int:
return 2
# Container protocol
def __getitem__(self, index: int) -> float:
if index == 0:
return self.x
if index == 1:
return self.y
raise IndexError("Index out of range")
# Iterator protocol
def __iter__(self):
yield self.x
yield self.y
# Boolean
def __bool__(self) -> bool:
return self.x != 0 or self.y != 0
# Context manager
def __enter__(self):
print(f"Working with {self}")
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print(f"Done with {self}")
return False
# Usage
v1 = Vector(3, 4)
v2 = Vector(1, 2)
print(v1 + v2) # Vector(4, 6)
print(abs(v1)) # 5.0
print(list(v1)) # [3, 4]
print(v1[0]) # 3.0
print(bool(Vector(0, 0))) # False# ── Descriptors ──
class Validated:
"""Descriptor that validates a value on assignment."""
def __init__(self, name: str, type_: type, min_val=None, max_val=None):
self.name = name
self.type_ = type_
self.min_val = min_val
self.max_val = max_val
def __set_name__(self, owner, name):
self.private_name = f"_{name}"
def __get__(self, obj, objtype=None):
if obj is None:
return self
return getattr(obj, self.private_name, None)
def __set__(self, obj, value):
if not isinstance(value, self.type_):
raise TypeError(f"{self.name} must be {self.type_.__name__}")
if self.min_val is not None and value < self.min_val:
raise ValueError(f"{self.name} must be >= {self.min_val}")
if self.max_val is not None and value > self.max_val:
raise ValueError(f"{self.name} must be <= {self.max_val}")
setattr(obj, self.private_name, value)
class Product:
name = Validated("name", str)
price = Validated("price", (int, float), min_val=0)
quantity = Validated("quantity", int, min_val=0)
p = Product()
p.name = "Widget" # OK
p.price = 29.99 # OK
p.price = -5 # ValueError!| Method | Trigger | Example |
|---|---|---|
| __init__ | Class() | obj = MyClass(1, 2) |
| __repr__ | repr(obj) | Dev-friendly string |
| __str__ | str(obj) | User-friendly string |
| __eq__ | a == b | Equality check |
| __hash__ | hash(obj) | Dict/set key |
| __len__ | len(obj) | Length protocol |
| __getitem__ | obj[key] | Indexing / slicing |
| __iter__ | iter(obj) | Iterator protocol |
| __next__ | next(obj) | Next iteration |
| __call__ | obj() | Callable object |
| __enter__/__exit__ | with obj: | Context manager |
| __contains__ | x in obj | Membership test |
class Optimized:
__slots__ = ("x", "y")
def __init__(self, x, y):
self.x = x
self.y = y
# No __dict__ — memory efficient
# self.z = 0 # AttributeError!super().__init__()instead of the parent class directly. Python's C3 linearization ensures every class in the diamond is initialized exactly once.import asyncio
# ── Async Functions ──
async def fetch_data(url: str) -> dict:
"""Simulate an async HTTP request."""
await asyncio.sleep(1) # non-blocking sleep
return {"url": url, "status": 200}
async def process_data(data: dict) -> str:
"""Process fetched data."""
await asyncio.sleep(0.5)
return f"Processed {data['url']}"
# ── Running Async Code ──
async def main():
# Sequential execution (awaits one at a time)
result = await fetch_data("https://api.example.com")
print(result)
# Concurrent execution with gather
urls = [
"https://api.example.com/users",
"https://api.example.com/posts",
"https://api.example.com/comments",
]
results = await asyncio.gather(
fetch_data(urls[0]),
fetch_data(urls[1]),
fetch_data(urls[2]),
)
print(results) # All results in order
# Concurrent with create_task (fire and control)
task1 = asyncio.create_task(fetch_data("url1"))
task2 = asyncio.create_task(fetch_data("url2"))
r1 = await task1 # wait for task1
r2 = await task2 # wait for task2
# Entry point
if __name__ == "__main__":
asyncio.run(main())import asyncio
# ── asyncio.wait — more control than gather ──
async def fetch_with_timeout():
tasks = [
asyncio.create_task(fetch_data(f"url{i}"))
for i in range(5)
]
# FIRST_COMPLETED: return as soon as one finishes
done, pending = await asyncio.wait(
tasks,
timeout=3.0,
return_when=asyncio.FIRST_COMPLETED,
)
# Cancel remaining
for task in pending:
task.cancel()
for task in done:
print(task.result())
# ── async for — async iteration ──
async def async_range(n: int):
for i in range(n):
await asyncio.sleep(0.1)
yield i
async def consume_async_iter():
async for value in async_range(5):
print(value)
# ── async with — async context managers ──
class AsyncResource:
async def __aenter__(self):
print("Acquiring resource...")
await asyncio.sleep(0.1)
return self
async def __aexit__(self, exc_type, exc_val, exc_tb):
print("Releasing resource...")
await asyncio.sleep(0.1)
return False
async def use_resource():
async with AsyncResource() as resource:
print("Using resource")
# ── Async Generator ──
async def data_stream(count: int):
"""Yield data items asynchronously."""
for i in range(count):
await asyncio.sleep(0.05)
yield {"id": i, "value": i ** 2}
async def collect_stream():
results = []
async for item in data_stream(10):
results.append(item)
return resultsimport asyncio
import aiohttp # pip install aiohttp
# ── Real-World: Concurrent HTTP Requests ──
async def fetch_json(session: aiohttp.ClientSession, url: str) -> dict:
"""Fetch JSON from a URL."""
async with session.get(url, timeout=aiohttp.ClientTimeout(total=10)) as resp:
resp.raise_for_status()
return await resp.json()
async def fetch_all(urls: list[str]) -> list[dict]:
"""Fetch multiple URLs concurrently."""
async with aiohttp.ClientSession() as session:
tasks = [fetch_json(session, url) for url in urls]
return await asyncio.gather(*tasks, return_exceptions=True)
async def main():
urls = [
"https://jsonplaceholder.typicode.com/posts/1",
"https://jsonplaceholder.typicode.com/posts/2",
"https://jsonplaceholder.typicode.com/posts/3",
]
results = await fetch_all(urls)
for result in results:
if isinstance(result, Exception):
print(f"Error: {result}")
else:
print(f"Title: {result['title'][:50]}")
# ── Semaphore: Limit Concurrent Requests ──
async def fetch_with_semaphore(urls: list[str], max_concurrent: int = 5):
"""Limit to max_concurrent simultaneous requests."""
semaphore = asyncio.Semaphore(max_concurrent)
async def bounded_fetch(session: aiohttp.ClientSession, url: str):
async with semaphore:
return await fetch_json(session, url)
async with aiohttp.ClientSession() as session:
tasks = [bounded_fetch(session, url) for url in urls]
return await asyncio.gather(*tasks)# ── concurrent.futures — Thread/Process Pools ──
import concurrent.futures
import time
import math
def cpu_bound(n: int) -> int:
"""Heavy computation — use ProcessPoolExecutor."""
return sum(math.factorial(i) for i in range(n))
def io_bound(url: str) -> str:
"""I/O operation — use ThreadPoolExecutor."""
import urllib.request
with urllib.request.urlopen(url, timeout=5) as resp:
return resp.read()[:100]
# Thread pool for I/O-bound tasks
def thread_pool_example():
urls = [f"https://httpbin.org/get?id={i}" for i in range(10)]
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
future_to_url = {
executor.submit(io_bound, url): url for url in urls
}
for future in concurrent.futures.as_completed(future_to_url):
url = future_to_url[future]
try:
data = future.result()
print(f"Got {len(data)} bytes from {url}")
except Exception as e:
print(f"{url} failed: {e}")
# Process pool for CPU-bound tasks
def process_pool_example():
numbers = [100, 200, 300, 400, 500]
with concurrent.futures.ProcessPoolExecutor() as executor:
results = list(executor.map(cpu_bound, numbers))
print(results)
# ── Threading Basics ──
import threading
class ThreadSafeCounter:
def __init__(self):
self._value = 0
self._lock = threading.Lock()
def increment(self) -> int:
with self._lock:
self._value += 1
return self._value
@property
def value(self) -> int:
return self._value| Model | Best For | GIL? | Overhead |
|---|---|---|---|
| asyncio | I/O-bound (network, files) | Not affected | Very low |
| ThreadPool | I/O-bound (blocking libs) | Yes | Low |
| ProcessPool | CPU-bound (heavy math) | No | High |
| threading | Legacy I/O-bound code | Yes | Low |
time.sleep() with async code — it blocks the event loop. Always use await asyncio.sleep(). For CPU-bound work in async, offload to a thread: await asyncio.to_thread(cpu_function).# ── pathlib — Modern file path handling (Python 3.4+) ──
from pathlib import Path, PurePath
# Creating paths (cross-platform)
project = Path("src") / "components" / "App.tsx"
config = Path.home() / ".config" / "myapp" / "settings.json"
# Path operations
project.exists() # bool
project.is_file() # bool
project.is_dir() # bool
project.stem # "App" (filename without ext)
project.suffix # ".tsx"
project.name # "App.tsx"
project.parent # Path("src/components")
project.parents[1] # Path("src")
# Reading & writing
content = Path("data.txt").read_text(encoding="utf-8")
data = Path("data.json").read_bytes()
Path("output.txt").write_text("Hello!", encoding="utf-8")
Path("binary.dat").write_bytes(b"\x00\x01\x02")
# Globbing
all_py = Path(".").glob("**/*.py") # recursive generator
all_py_list = list(Path(".").rglob("*.py")) # same, returns list
toml_files = Path(".").glob("**/*.toml")
# Directory operations
Path("new_dir").mkdir(parents=True, exist_ok=True)
Path("old_dir").rmdir() # only empty dirs
# ── os — lower-level system interface ──
import os
os.environ.get("API_KEY", "default") # env variable
os.getcwd() # current directory
os.chdir("/tmp") # change directory
os.path.join("dir", "file.txt") # path join (use pathlib instead!)
os.path.exists("/etc/passwd") # exists check
os.path.getsize("file.txt") # file size in bytes# ── json — JSON encoding/decoding ──
import json
data = {
"name": "Alice",
"age": 30,
"skills": ["Python", "Go", "Rust"],
"address": {"city": "NYC", "zip": "10001"},
}
# Encoding
json_str = json.dumps(data, indent=2, sort_keys=True)
json_bytes = json.dumps(data).encode("utf-8")
with open("data.json", "w") as f:
json.dump(data, f, indent=2, ensure_ascii=False)
# Decoding
parsed = json.loads(json_str)
with open("data.json") as f:
parsed2 = json.load(f)
# Custom serialization
from datetime import datetime
class CustomEncoder(json.JSONEncoder):
def default(self, obj):
if isinstance(obj, datetime):
return obj.isoformat()
if isinstance(obj, set):
return list(obj)
return super().default(obj)
json.dumps({"ts": datetime.now()}, cls=CustomEncoder)
# ── re — Regular Expressions ──
import re
# Pattern matching
email_pattern = r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}"
emails = re.findall(email_pattern, text)
# Search & match
match = re.search(r"\b(\d{3})[-.]?(\d{3})[-.]?(\d{4})\b", phone_text)
if match:
area, prefix, line = match.groups()
full = match.group(0)
# Substitution
cleaned = re.sub(r"<[^>]+>", "", html_text) # strip HTML tags
normalized = re.sub(r"\s+", " ", text.strip()) # normalize whitespace
# Compilation (for reuse)
url_pattern = re.compile(r"https?://[\w./-]+")
urls = url_pattern.findall(document)
# Named groups
log_pattern = re.compile(
r"(?P<timestamp>\d{4}-\d{2}-\d{2}) "
r"(?P<level>\w+) "
r"(?P<message>.+)"
)
for m in log_pattern.finditer(log_text):
print(f"{m.group('level')}: {m.group('message')}")# ── datetime — Date & Time ──
from datetime import datetime, date, time, timedelta, timezone
now = datetime.now(timezone.utc) # timezone-aware now
today = date.today() # current date
dt = datetime(2024, 12, 25, 10, 30, tzinfo=timezone.utc)
# Parsing & formatting
parsed = datetime.strptime("2024-12-25", "%Y-%m-%d")
formatted = now.strftime("%Y-%m-%d %H:%M:%S %Z")
iso = now.isoformat() # ISO 8601
# Arithmetic
tomorrow = today + timedelta(days=1)
next_week = now + timedelta(weeks=1)
diff = datetime(2025, 1, 1) - now # timedelta
# ── itertools — Iterator building blocks ──
from itertools import (
chain, islice, cycle, repeat, count,
product, permutations, combinations,
combinations_with_replacement, groupby, accumulate
)
# Chaining iterables
list(chain([1, 2], [3, 4], [5])) # [1, 2, 3, 4, 5]
list(chain.from_iterable([[1, 2], [3], [4, 5]]))
# Slicing (works on any iterator, not just lists)
list(islice(range(100), 10, 20, 2)) # [10, 12, 14, 16, 18]
# Combinatorics
list(product("AB", "CD")) # [('A','C'),('A','D'),('B','C'),('B','D')]
list(permutations("ABC", 2)) # [('A','B'),('A','C'),('B','A'),...]
list(combinations("ABC", 2)) # [('A','B'),('A','C'),('B','C')]
# Running totals
list(accumulate([1, 2, 3, 4, 5])) # [1, 3, 6, 10, 15]
# Group consecutive items
data = [(1, 'a'), (1, 'b'), (2, 'c'), (2, 'd')]
for key, group in groupby(data, key=lambda x: x[0]):
print(f"{key}: {[item for item in group]}")# ── logging ──
import logging
# Configure (typically at module level)
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
datefmt="%Y-%m-%d %H:%M:%S",
handlers=[
logging.FileHandler("app.log"),
logging.StreamHandler(),
]
)
logger = logging.getLogger(__name__)
logger.debug("Detailed debug info")
logger.info("General info message")
logger.warning("Something unexpected happened")
logger.error("Something went wrong!")
logger.critical("System failure!")
# ── argparse — CLI argument parsing ──
import argparse
parser = argparse.ArgumentParser(
description="Process some files.",
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
)
parser.add_argument("input", help="Input file path")
parser.add_argument("-o", "--output", default="out.txt", help="Output file")
parser.add_argument("-n", "--num", type=int, default=10, help="Number of items")
parser.add_argument("-v", "--verbose", action="store_true", help="Verbose mode")
parser.add_argument("--sort", choices=["asc", "desc"], default="asc")
parser.add_argument("files", nargs="+", help="Files to process")
args = parser.parse_args()
print(f"Processing {args.files} -> {args.output}")
# ── subprocess — Run external commands ──
import subprocess
result = subprocess.run(
["git", "status", "--short"],
capture_output=True,
text=True,
check=True,
timeout=30,
)
print(result.stdout)
# Streaming output
process = subprocess.Popen(
["python", "-u", "server.py"],
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
)
for line in process.stdout:
print(line, end="")# ── typing — Advanced Type Hints ──
from typing import Protocol, TypedDict, Literal, TypeGuard, TypeVar, Any
from typing import overload, final, NotRequired
# Protocol — Structural subtyping (duck typing with types)
class Drawable(Protocol):
def draw(self) -> str: ...
def resize(self, factor: float) -> None: ...
class Circle:
def draw(self) -> str:
return "circle"
def resize(self, factor: float) -> None:
pass
def render(item: Drawable) -> None:
print(item.draw()) # Any class with draw() + resize() works
# TypedDict — Dict with specific keys (type-checked)
class User(TypedDict):
name: str
age: int
email: str
active: NotRequired[bool] # Optional key (Python 3.11+)
def process_user(user: User) -> str:
return f"{user['name']} ({user['age']})"
# Literal — specific values only
def http_method(method: Literal["GET", "POST", "PUT", "DELETE"]) -> None:
print(f"Method: {method}")
http_method("GET") # OK
http_method("PATCH") # Type error!
# TypeGuard — custom type narrowing
from collections.abc import Sequence
def is_sequence_of_ints(val: Any) -> TypeGuard[Sequence[int]]:
return isinstance(val, Sequence) and all(isinstance(x, int) for x in val)
items: Any = [1, 2, 3]
if is_sequence_of_ints(items):
reveal_type(items) # Sequence[int]
# @final — prevent override
class BaseService:
@final
def authenticate(self) -> bool:
return True
# @overload — multiple signatures
@overload
def process(data: str) -> str: ...
@overload
def process(data: bytes) -> bytes: ...
@overload
def process(data: list[str]) -> dict[str, str]: ...
def process(data):
if isinstance(data, str):
return data.upper()
if isinstance(data, bytes):
return data.upper()
return {item: item.upper() for item in data}| Module | Purpose | Key Functions |
|---|---|---|
| os / pathlib | File system | Path, environ, chdir |
| json | JSON handling | loads, dumps, load, dump |
| re | Regex | findall, match, sub, compile |
| datetime | Dates & times | now, strftime, timedelta |
| itertools | Iterators | chain, product, groupby |
| functools | Function tools | lru_cache, partial, wraps |
| logging | Logging | getLogger, debug, info, error |
| argparse | CLI parsing | ArgumentParser, add_argument |
| subprocess | Shell commands | run, Popen, PIPE |
| statistics | Stats | mean, median, stdev |
| typing | Type hints | Protocol, TypedDict, Literal |
| collections | Data structures | defaultdict, Counter |
| dataclasses | Class boilerplate | @dataclass decorator |
| contextlib | Context managers | contextmanager, suppress |
| tempfile | Temp files | NamedTemporaryFile, mkdtemp |
from contextlib import suppress
from itertools import batched
# Suppress specific exceptions
with suppress(FileNotFoundError):
os.remove("maybe_exists.txt")
# Batched iteration (Python 3.12+)
for batch in batched(range(10), 3):
print(batch)
# (0, 1, 2), (3, 4, 5), (6, 7, 8), (9,)pathlib over os.path, dataclasses over manual __init__, logging over print().import sys
lst = [1, 2, 3]
tpl = (1, 2, 3)
print(sys.getsizeof(lst)) # ~88 bytes
print(sys.getsizeof(tpl)) # ~72 bytes
print(hash(tpl)) # OK — hashable
print(hash(lst)) # TypeError! unhashable typeThe Global Interpreter Lock (GIL) is a mutex that allows only one thread to execute Python bytecode at a time. This means multi-threading cannot achieve true parallelism for CPU-bound tasks.
import threading
import multiprocessing
# Threads — runs sequentially due to GIL (CPU-bound)
def cpu_task(n):
return sum(i**2 for i in range(n))
# Use processes for CPU parallelism
with multiprocessing.Pool(4) as pool:
results = pool.map(cpu_task, [10**6] * 4) # True parallelismA decorator is a function that takes another function and extends its behavior without modifying its source code. Decorators use closures and are applied with the @ syntax.
import functools
import time
def validate_types(**expected_types):
"""Decorator factory that validates argument types at runtime."""
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
# Combine positional and keyword args with parameter names
sig = functools.signature(func)
bound = sig.bind(*args, **kwargs)
bound.apply_defaults()
for param_name, value in bound.arguments.items():
if param_name in expected_types:
if not isinstance(value, expected_types[param_name]):
raise TypeError(
f"{func.__name__}() expected "
f"{param_name!r} to be "
f"{expected_types[param_name]}, "
f"got {type(value).__name__}"
)
return func(*args, **kwargs)
return wrapper
return decorator
@validate_types(name=str, age=int, active=bool)
def create_user(name: str, age: int, active: bool = True) -> dict:
return {"name": name, "age": age, "active": active}
create_user("Alice", 30, True) # OK
create_user("Bob", "30", True) # TypeError!A generator is a function that uses yield to produce values lazily, one at a time. Unlike lists, generators do not store all values in memory — they compute each value on demand.
# Generator function — uses yield
def fibonacci(n: int):
a, b = 0, 1
for _ in range(n):
yield a
a, b = b, a + b
# Generator expression — lazy comprehension
squares = (x**2 for x in range(1_000_000))
# Key differences:
# 1. Memory: generator is O(1), list is O(n)
# 2. Lazy: values computed on demand
# 3. Single pass: can only iterate once
# 4. Supports next(), send(), throw(), close()
# Send value into generator (coroutine pattern)
def accumulator():
total = 0
while True:
value = yield total
if value is None:
break
total += value
gen = accumulator()
next(gen) # Prime the generator -> 0
gen.send(10) # -> 10
gen.send(20) # -> 30
gen.send(5) # -> 35
gen.close() # StopIterationimport gc
import sys
# Reference counting
a = [1, 2, 3]
print(sys.getrefcount(a)) # 2 (a + getrefcount arg)
b = a
print(sys.getrefcount(a)) # 3
del b
print(sys.getrefcount(a)) # 2
# Circular reference (GC handles this)
class Node:
def __init__(self, name):
self.name = name
self.parent = None
self.children = []
n1 = Node("parent")
n2 = Node("child")
n1.children.append(n2)
n2.parent = n1 # circular ref!
del n1, n2 # objects still alive due to cycle
gc.collect() # force collection — frees them
# Disabling GC for performance (rare, in tight loops)
gc.disable()
# ... perform many allocations ...
gc.enable()def example(a, b, /, c, d=10, *args, key, **kwargs):
"""
a, b — positional-only (before /)
c — positional or keyword
d — keyword (has default)
*args — captures extra positional args as tuple
key — keyword-only (after *)
**kwargs — captures extra keyword args as dict
"""
pass
# Valid calls:
example(1, 2, 3, key="value", extra=42)
example(1, 2, c=3, key="value")
# Common patterns:
def wrapper(*args, **kwargs):
"""Forward all arguments to another function."""
return target_function(*args, **kwargs)
def merge(*dicts: dict) -> dict:
"""Merge multiple dicts into one."""
result = {}
for d in dicts:
result.update(d)
return result
print(merge({"a": 1}, {"b": 2}, {"c": 3}))
# {'a': 1, 'b': 2, 'c': 3}A metaclassis the class of a class — it controls how classes are created. When Python creates a class, it calls the metaclass's __new__ and __init__.
# Metaclass that validates class attributes
class ValidateFields(type):
"""Ensure all class attributes with _type suffix have validators."""
def __new__(mcs, name, bases, namespace):
# Auto-register classes
if "__abstract__" not in namespace:
namespace["__concrete__"] = True
cls = super().__new__(mcs, name, bases, namespace)
return cls
class Singleton(type):
"""Metaclass that ensures only one instance exists."""
_instances: dict = {}
def __call__(cls, *args, **kwargs):
if cls not in cls._instances:
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
class Database(metaclass=Singleton):
def __init__(self, url: str):
self.url = url
# Always returns the same instance
db1 = Database("postgresql://...")
db2 = Database("postgresql://...")
assert db1 is db2 # True
# NOTE: In modern Python, prefer __init_subclass__,
# class decorators, or descriptors over metaclasses.
# Metaclasses add complexity and are hard to debug.class User:
def __init__(self, name: str, email: str):
self.name = name
self.email = email
def __str__(self) -> str:
return f"{self.name} <{self.email}>"
def __repr__(self) -> str:
return f"User(name={self.name!r}, email={self.email!r})"
u = User("Alice", "alice@example.com")
print(str(u)) # Alice <alice@example.com>
print(repr(u)) # User(name='Alice', email='alice@example.com')
print(u) # Alice <alice@example.com> (uses __str__)
# In a list or REPL, __repr__ is used:
print([u]) # [User(name='Alice', email='alice@example.com')]Context managers handle setup and teardown via the with statement. They guarantee cleanup code runs even if exceptions occur.
# Using @contextmanager — simplest way to create one
from contextlib import contextmanager
@contextmanager
def database_connection(url: str):
"""Custom context manager for a database connection."""
conn = connect(url)
try:
conn.begin_transaction()
yield conn # value in 'as' clause
conn.commit()
except Exception:
conn.rollback()
raise
finally:
conn.close()
# Class-based context manager
class Timer:
"""Measure execution time of a code block."""
def __enter__(self):
self.start = time.perf_counter()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.elapsed = time.perf_counter() - self.start
print(f"Elapsed: {self.elapsed:.4f}s")
return False # don't suppress exceptions
with Timer() as t:
time.sleep(0.5)
# Elapsed: 0.5001s
print(t.elapsed)Python 3.10+ introduced structural pattern matching with match/case, going far beyond simple switch/case. Python 3.12+ added improvements to patterns.
# Structural pattern matching (Python 3.10+)
def handle_command(command: str | dict) -> str:
match command:
# Literal patterns
case "quit" | "exit":
return "Goodbye!"
case "help":
return "Showing help..."
# Pattern with capture
case str() as text if len(text) > 100:
return f"Long command ({len(text)} chars)"
# Dict pattern (structural matching)
case {"action": "create", "type": type_, **rest}:
return f"Creating {type_} with {rest}"
# Sequence pattern
case [first, *middle, last]:
return f"First: {first}, Last: {last}"
# Class pattern
case Point(x=0, y=0):
return "Origin"
# Guard clause
case int() as n if n < 0:
return "Negative number"
# Wildcard (default)
case _:
return f"Unknown command: {command!r}"
# Guard patterns (Python 3.12+ improvements)
match value:
case x if isinstance(x, int) and x > 0:
print("Positive int")
case _:
print("Something else")| Topic | Key Points | Difficulty |
|---|---|---|
| GIL & Threading | One thread executes bytecode at a time; use multiprocessing for CPU-bound | Medium |
| Decorators | Functions that wrap/extend other functions using closures | Medium |
| Generators | Lazy evaluation with yield; O(1) memory; single-pass iteration | Medium |
| GC & Memory | Reference counting + cyclic GC; weakref for caches | Hard |
| Metaclasses | Class of a class; controls class creation; prefer alternatives | Hard |
| Context Managers | with statement; __enter__/__exit__ or @contextmanager | Medium |
| match/case | Structural pattern matching; literals, dicts, sequences, guards | Medium |
| Descriptors | __get__/__set__ for attribute access control; @property is a descriptor | Hard |
| Type Hints | Protocol, TypedDict, Literal, TypeGuard; Python 3.12+ type params | Medium |
| asyncio | Event loop, coroutines, await, gather; use for I/O-bound concurrency | Hard |