Python Complete Advanced Edition
Python Complete Advanced Edition
Python is dynamically typed — you assign a value, Python infers the type. Variables are just
names pointing to objects in memory.
# Multiple assignment
x = y = z = 0
a, b, c = 1, 2, 3 # unpacking
a, b = b, a # swap in one line
first, *rest = [1,2,3,4,5] # starred assignment
# first=1, rest=[2,3,4,5]
# Complex numbers
z = 3 + 4j
print([Link], [Link]) # 3.0 4.0
print(abs(z)) # 5.0 (magnitude)
# f-strings (3.6+)
name = "Bob"
msg = f"Hello {name!r}, age {2024-1994}"
# Slicing
s = "abcdefgh"
print(s[2:5]) # cde
print(s[::-1]) # hgfedcba
print(s[::2]) # aceg
# dict — key-value
person = {"name":"Alice","age":30}
person["email"] = "a@[Link]"
print([Link]("phone","N/A")) # N/A
for k,v in [Link](): print(f"{k}: {v}")
# Inspection
x = [1, 2, 3]
print(type(x)) # <class 'list'>
print(isinstance(x, list))# True
print(id(x)) # memory address
💡 Mutable vs Immutable
str, int, float, bool, tuple, frozenset are immutable — operations return new objects. list, dict,
set are mutable — modified in place.
✏ Practice Exercises
1. Create variables for your name, age, height, and a list of 3 hobbies. Print each with
type().
2. Demonstrate starred assignment: unpack [1,2,3,4,5] into first, middle (list), last.
3. Show 5 string methods on " The Quick Brown Fox " — strip, lower, split, count,
replace.
4. Create two sets (favourite movies, recommended movies). Find overlap, movies only
you like, and the union.
5. Show why 0.1 + 0.2 != 0.3 and fix it using the decimal module.
6. Build a dict of a person, iterate over it, update two fields, and delete one key.
7. Write a program using all numeric types: int, float, complex — perform arithmetic
across them.
Chapter 2
Operators
Arithmetic, comparison, logical, bitwise & more
2.1 Arithmetic
a, b = 17, 5
print(a + b) # 22 addition
print(a - b) # 12 subtraction
print(a * b) # 85 multiplication
print(a / b) # 3.4 true division
print(a // b) # 3 floor division
print(a % b) # 2 modulo
print(a ** b) # 1419857 exponentiation
2.3 Logical
print(True and False) # False
print(True or False) # True
print(not True) # False
# Short-circuit: second operand skipped if result is decided
x = None
name = x or "Default" # "Default"
y = [] or {} or 0 or "fallback" # "fallback"
2.4 Bitwise
a, b = 0b1010, 0b1100 # 10 and 12
print(bin(a & b)) # 0b1000 AND
print(bin(a | b)) # 0b1110 OR
print(bin(a ^ b)) # 0b0110 XOR
print(bin(~a)) # -0b1011 NOT
print(bin(a << 2)) # 0b101000 left shift (x4)
print(bin(a >> 1)) # 0b101 right shift (÷2)
# Power of 2 check
def is_power_of_2(n): return n > 0 and (n & (n-1)) == 0
print(is_power_of_2(8), is_power_of_2(6)) # True False
# Identity
a = [1,2]; b = a; c = [1,2]
print(a is b) # True — same object
print(a is c) # False — equal value, different object
# Membership
print("py" in "python") # True
print(3 in [1,2,3]) # True
print("x" not in {"a","b"}) # True
✏ Practice Exercises
8. Use divmod() to convert 1000 minutes into hours and minutes.
9. Write is_even() and is_odd() using only the bitwise & operator.
10. Demonstrate the walrus operator in a while loop that reads non-empty user input.
11. Build a simple bit-flag permission system: READ=1, WRITE=2, EXEC=4. Set, check,
and revoke flags.
12. Show 3 examples where Python short-circuit evaluation prevents a runtime error.
13. Write a chained comparison that validates a score is between 0 and 100 inclusive in
one expression.
Chapter 3
Control Flow
if/elif/else, for, while, comprehensions & match
# Ternary
status = "pass" if score >= 60 else "fail"
match command:
case ("quit"):
print("quitting")
case ("move", x, y):
print(f"move to ({x},{y})")
case ("fire", direction) if direction in ("N","S","E","W"):
print(f"fire {direction}")
case _:
print("unknown command")
# Match on types
def process(value):
match value:
case int() | float() as n if n > 0:
return f"positive number: {n}"
case str() as s if [Link]("err"):
return f"error string: {s}"
case list() as lst:
return f"list with {len(lst)} items"
case _:
return "unknown"
# enumerate
names = ["Alice","Bob","Carol"]
for i, name in enumerate(names, start=1):
print(f"{i}. {name}")
# zip
scores = [85,92,78]
for name,score in zip(names,scores):
print(f"{name}: {score}")
# zip_longest
from itertools import zip_longest
for a,b in zip_longest([1,2,3],[4,5],fillvalue=0):
print(a,b)
# Sentinel pattern
while (line := input("> ")) != "quit":
print("Got:", line)
✏ Practice Exercises
14. FizzBuzz 1–100: Fizz÷3, Buzz÷5, FizzBuzz÷15, else the number.
15. Write a menu-driven calculator using match/case for operations.
16. Print a 10×10 multiplication table using nested for loops.
17. Write a prime sieve (Sieve of Eratosthenes) up to N using loops and lists.
18. Use for…else to search a list for a target — print "found" or "not found" without a flag
variable.
19. Collect user input lines until a blank line, then print sorted unique words and their
frequencies.
20. Write a number guessing game (1–100) with hints and a 7-guess limit.
Chapter 4
Functions
Parameters, defaults, *args, **kwargs, closures
# Keyword-only (after *)
def resize(width, height, *, scale=1.0, dpi=96):
return int(width*scale*dpi), int(height*scale*dpi)
# Positional-only (before /)
def circle_area(r, /):
import math; return [Link] * r**2
# Full signature
def full(pos_only, /, normal, *, kw_only="a", **extra):
pass
lo, hi = min_max([3,1,7,2,9])
# Early return
def safe_div(a, b):
if b == 0: return None
return a / b
def outer():
x = "enclosing"
def inner():
x = "local"
print(x) # local
inner()
print(x) # enclosing
outer()
print(x) # global
# global / nonlocal
counter = 0
def inc():
global counter
counter += 1
def make_counter():
n = 0
def bump():
nonlocal n; n += 1; return n
return bump
c = make_counter()
print(c(), c(), c()) # 1 2 3
4.5 Closures
def multiplier(factor):
def multiply(n): # captures factor
return n * factor
return multiply
double = multiplier(2)
triple = multiplier(3)
print(double(5), triple(5)) # 10 15
# Closure cells
print(double.__code__.co_freevars) # ('factor',)
print(double.__closure__[0].cell_contents) # 2
4.6 Decorators
from functools import wraps
import time
def timer(func):
@wraps(func)
def wrapper(*args, **kwargs):
t = time.perf_counter()
result = func(*args, **kwargs)
print(f"{func.__name__}: {time.perf_counter()-t:.4f}s")
return result
return wrapper
@timer
@retry(times=3, exceptions=(ValueError,))
def risky(x):
if x < 0: raise ValueError("negative")
return x**2
@lru_cache(maxsize=None)
def fib(n):
if n < 2: return n
return fib(n-1) + fib(n-2)
# reduce
from functools import reduce
product = reduce(lambda acc,x: acc*x, [1,2,3,4,5]) # 120
✏ Practice Exercises
21. Write a memoize decorator from scratch (without functools). It should cache results by
args.
22. Write a @validate decorator that checks all numeric arguments are positive before
calling the function.
23. Implement partial() from scratch using closures.
24. Write a function pipeline(*funcs) that returns a function applying funcs left-to-right.
25. Create a @once decorator that ensures a function runs only the first time and returns
cached result afterwards.
26. Build a curried add function: add(1)(2)(3) returns 6.
27. Write a @deprecated(msg) decorator that prints a DeprecationWarning then calls the
function.
Chapter 5
Classes & OOP
__init__, inheritance, polymorphism, dunder methods
def bark(self):
return f"{[Link]}: Woof!"
def __repr__(self):
return f"Dog({[Link]!r}, {[Link]})"
def __str__(self):
return f"{[Link]} (age {[Link]})"
rex = Dog("Rex", 3)
print(repr(rex)) # Dog('Rex', 3)
print(str(rex)) # Rex (age 3)
class Cat(Animal):
def speak(self): return f"{[Link]}: Meow!"
class Dog(Animal):
def __init__(self, name, breed):
super().__init__(name)
[Link] = breed
def speak(self): return f"{[Link]}: Woof!"
# Polymorphism
for a in [Cat("Whiskers"), Dog("Rex","Lab")]:
print([Link]())
# Multiple inheritance
class Flyable:
def fly(self): return "I can fly"
@[Link]
def celsius(self, val):
if val < -273.15: raise ValueError("Below absolute zero")
self._c = val
@property
def fahrenheit(self): return self._c * 9/5 + 32
@[Link]
def fahrenheit(self, val): self._c = (val-32)*5/9
@property
def kelvin(self): return self._c + 273.15
@classmethod
def from_fahrenheit(cls, f): return cls((f-32)*5/9)
@staticmethod
def is_valid(c): return c >= -273.15
t = Temperature(100)
print([Link]) # 212.0
t2 = Temperature.from_fahrenheit(32)
print([Link]) # 0.0
✏ Practice Exercises
28. Build a BankAccount class with deposit, withdraw (raises InsufficientFunds), balance
property, and transaction history.
29. Create a Shape hierarchy: Shape (abstract) → Circle, Rectangle, Triangle with area(),
perimeter(), __gt__, __lt__.
30. Implement a Fraction class with full arithmetic (+,-,*,/) and comparison operators.
Always reduce to lowest terms.
31. Write a Matrix class: __add__, __mul__ (matrix & scalar), __getitem__, __setitem__,
transpose(), __repr__.
32. Create a LinkedList class with append, prepend, remove, __iter__, __len__,
__contains__, __repr__.
33. Implement a mixin class Serializable that adds to_json() and from_json() to any class
using __dict__.
34. Design a role-based Employee hierarchy: Employee → Manager, Engineer.
Manager.add_report() / reports property.
Chapter 6
Modules & Packages
import, packages, __init__.py, pip, virtual envs
6.1 Importing
import math
import math as m # alias
from math import pi, sqrt # specific names
from math import * # all — avoid in production
# Conditional import
try:
import ujson as json
except ImportError:
import json
def circle_area(r):
return PI * r ** 2
def is_prime(n):
if n < 2: return False
return all(n % i for i in range(2, int(n**0.5)+1))
if __name__ == "__main__":
# Only runs when called directly, not when imported
print(circle_area(5))
6.3 Packages
# mypkg/
# __init__.py
# shapes/
# __init__.py
# [Link]
# [Link]
# mypkg/__init__.py
from .[Link] import Circle
from .[Link] import Rectangle
__version__ = "1.0.0"
__all__ = ["Circle","Rectangle"] # limits "from pkg import *"
# Usage
from mypkg import Circle, Rectangle
from [Link] import Circle # explicit
# pip commands
# pip install requests
# pip install requests==2.31.0
# pip install "requests>=2.0,<3.0"
# pip install -e . (editable install)
# pip install -r [Link]
# pip freeze > [Link]
# pip list --outdated
# pip show requests
✏ Practice Exercises
35. Create a string_utils package with modules: [Link] (camel, snake, title converters),
[Link] (word_count, char_freq), [Link] (is_email, is_url).
36. Add a __version__, __author__, and __all__ to your package __init__.py. Verify "from
pkg import *" only exposes __all__.
37. Write a script that uses [Link] to temporarily add a directory, import a module from
it, then restore [Link].
38. Use importlib.import_module() to dynamically load a plugin based on a user-supplied
string.
39. Set up a virtual environment, install requests and beautifulsoup4, freeze
requirements, then recreate from the file.
Chapter 7
File I/O & Context Managers
open(), read/write, json, csv, pathlib
# Write (overwrites)
with open("[Link]", "w") as f:
[Link]("Hello\n")
[Link](["line1\n","line2\n"])
# Append
with open("[Link]", "a") as f:
[Link]("new entry\n")
# Binary
with open("[Link]","rb") as f:
data = [Link]()
# Querying
print([Link](), p.is_file(), [Link])
for f in Path(".").glob("**/*.py"):
print(f, [Link]().st_size, "bytes")
# JSON
data = {"name":"Alice","scores":[90,85]}
json_str = [Link](data, indent=2)
back = [Link](json_str)
Path("[Link]").write_text(json_str)
# CSV
rows = [["Name","Age"],["Alice",30],["Bob",25]]
with open("[Link]","w",newline="") as f:
[Link](f).writerows(rows)
with open("[Link]") as f:
for row in [Link](f):
print(row["Name"], row["Age"])
# Generator-based (simpler)
from contextlib import contextmanager
@contextmanager
def timer(label=""):
import time
start = time.perf_counter()
try:
yield
finally:
elapsed = time.perf_counter()-start
print(f"{label}: {elapsed:.4f}s")
✏ Practice Exercises
40. Write a program using pathlib to find all .py files recursively, count lines in each, print
a sorted summary.
41. Build a JSON-based contact book: add, list, search by name, delete. Persist between
runs.
42. Write a context manager @atomic_write(path) that writes to a temp file then renames
— guaranteeing no partial writes.
43. Parse a CSV of transactions (date, amount, category) — compute monthly totals and
write a summary CSV.
44. Implement a rotating log file context manager that creates a new file when the current
one exceeds 1 MB.
PART II
Advanced Python
Type hints, async, concurrency, metaclasses
Chapter 8
Error Handling
try/except/finally, custom exceptions, exception chains
class ValidationError(AppError):
def __init__(self, field, message, value=None):
[Link] = field
[Link] = value
super().__init__(f"[{field}] {message} (got {value!r})")
class DatabaseError(AppError):
pass
class RecordNotFoundError(DatabaseError):
def __init__(self, model, pk):
super().__init__(f"{model} with pk={pk} not found")
[Link], [Link] = model, pk
# Suppress chaining
raise DatabaseError("cannot fetch") from None # hides chain
✏ Practice Exercises
45. Write a safe_json_load(path) that returns {} on FileNotFoundError, None on
JSONDecodeError, and re-raises others.
46. Create a full custom exception hierarchy for a REST API: APIError → AuthError,
NotFoundError, RateLimitError.
47. Write a @catch_and_log(logger, default=None) decorator that catches Exception,
logs it, and returns default.
48. Implement try_parse(s, type_) that returns (value, None) on success, (None, error) on
failure (Result pattern).
49. Build a multi-step form validator that collects all validation errors before raising an
ExceptionGroup.
50. Write a function that demonstrates exception chaining (from e) vs suppression (from
None) with print output.
Chapter 9
Comprehensions, Generators & Lambda
List/dict/set comprehensions, generator expressions, functional tools
# Walrus in comprehension
data = [1,-2,3,-4,5]
pos_doubled = [y for x in data if (y:=x*2) > 0]
print(pos_doubled) # [2, 6, 10]
# reduce
from functools import reduce
fact = reduce(lambda acc,x: acc*x, range(1,6)) # 120
✏ Practice Exercises
51. Use a single list comprehension to extract all email addresses from a list of strings.
52. Build a comprehension-only word frequency counter from a paragraph of text.
53. Write a generator pipeline: read CSV → parse → filter rows where amount>100 →
yield formatted strings.
54. Recreate map(), filter(), and zip() as generator functions without importing anything.
55. Generate all Pythagorean triples (a,b,c) where a,b,c ≤ 50 using a list comprehension.
56. Use reduce() to flatten a list of lists into one list.
Chapter 10
Iterators & Generators
Iterator protocol, yield, send(), yield from, async generators
def __next__(self):
if [Link] > [Link]: raise StopIteration
val = [Link]
[Link] += [Link]
return val
fib = fibonacci()
print([next(fib) for _ in range(10)])
# [0,1,1,2,3,5,8,13,21,34]
def flatten(nested):
for item in nested:
if isinstance(item,(list,tuple)):
yield from flatten(item)
else:
yield item
print(list(flatten([1,[2,[3,4]],[5]]))) # [1,2,3,4,5]
gen = accumulator()
next(gen) # prime
print([Link](10)) # 10
print([Link](20)) # 30
print([Link](5)) # 35
# Environment
print([Link]("HOME","/tmp"))
[Link]["MY_VAR"] = "hello"
# File system
[Link]("a/b/c", exist_ok=True)
[Link]("[Link]","[Link]")
# Walk tree
for root,dirs,files in [Link]("."):
for f in files:
full = [Link](root,f)
print(full, [Link](full))
# pathlib (modern)
p = Path("data") / "[Link]"
print([Link], [Link], [Link])
matches = list(Path(".").rglob("*.py"))
11.2 sys
import sys
print([Link], [Link])
print([Link]) # CLI args
[Link](0,"./mods") # add to import path
print([Link]()) # 1000 default
[Link](2000)
# stdin/stdout/stderr
for line in [Link]:
[Link]([Link]())
# Exit codes
[Link](0) # 0 = success, nonzero = error
11.3 collections
from collections import
Counter,defaultdict,deque,namedtuple,OrderedDict,ChainMap
# Counter
words = "the quick brown fox jumps over the lazy dog".split()
c = Counter(words)
print(c.most_common(3)) # [('the',2),...]
[Link](["the","fox"])
print(+c) # elements with count>0
# defaultdict
dd = defaultdict(list)
for name,grade in [("Alice",90),("Bob",85),("Alice",88)]:
dd[name].append(grade)
# {"Alice":[90,88],"Bob":[85]}
# namedtuple
Point = namedtuple("Point",["x","y"])
p = Point(3,4); print(p.x, p._asdict())
11.4 itertools
import itertools as it
# Infinite
count = [Link](start=0, step=2) # 0,2,4,6...
cycle = [Link]("ABC") # A,B,C,A,B,C...
repeat = [Link](10, times=3) # 10,10,10
# Finite
print(list([Link]([1,2],[3,4],[5]))) # [1,2,3,4,5]
print(list([Link]("ABCDE",[1,0,1,0,1])))# [A,C,E]
print(list([Link](lambda x:x<3,[1,2,3,4])))
print(list([Link](lambda x:x<3,[1,2,3,4])))
print(list([Link](count, 5))) # [0,2,4,6,8]
# Combinatoric
print(list([Link]("AB",2))) # [(A,B),(B,A)]
print(list([Link]("ABC",2))) # [(A,B),(A,C),(B,C)]
print(list([Link]("AB","12"))) # (A1)(A2)(B1)(B2)
now = [Link]()
utc = [Link]([Link])
today = [Link]()
# Format
print([Link]("%Y-%m-%d %H:%M:%S"))
dt = [Link]("2024-01-15",""%Y-%m-%d")
# Arithmetic
tomorrow = today + timedelta(days=1)
week_ago = now - timedelta(weeks=1)
diff = datetime(2026,1,1) - now
print([Link],"days until 2026")
# Unix timestamp
ts = [Link]() # seconds since epoch
dt2 = [Link](ts)
pattern = r"\b[A-Z][a-z]+\b"
text = "Alice and Bob met Carol."
m = [Link](r"(\d{4})-(\d{2})-(\d{2})", "2024-01-15")
if m:
year,month,day = [Link]()
# Sub / subn
clean = [Link](r"\s+", " ", "too many spaces")
# Verbose mode
phone = [Link](r"""
(\d{3}) # area code
[-.] # separator
(\d{3}) # prefix
[-.] # separator
(\d{4}) # line number
""", [Link])
✏ Practice Exercises
63. Use Counter + most_common() to build a word frequency report for a text file, sorted
by frequency.
64. Use defaultdict(set) to build an inverted index: {word → set of line numbers} from a
file.
65. Build all 5-card poker hands (no repeats) from a 52-card deck using
[Link]. Count flush hands.
66. Use re to validate and parse email addresses, phone numbers, and ISO dates from a
block of text.
67. Write a CLI tool using [Link] that accepts --sort, --filter=PATTERN flags to process
a CSV file.
68. Use groupby to aggregate a list of sales records by month, computing total and
average per month.
Chapter 12
Type Hints & Static Typing
PEP 484, mypy, generics, Protocol, TypeVar
Type hints make Python code self-documenting and catch bugs before runtime. They are purely
advisory at runtime — use mypy or pyright to enforce them.
age: int = 30
labels: list[str] = ["a","b","c"] # Python 3.9+
mapping: dict[str, int] = {"x":1}
pair: tuple[int, str] = (1,"a") # fixed-length tuple
coords: tuple[float, ...] = (1.0,2.0,3.0) # variable length
class Stack(Generic[T]):
def __init__(self) -> None:
self._items: list[T] = []
s: Stack[int] = Stack()
[Link](1); [Link](2)
print([Link]()) # 2
@runtime_checkable
class Drawable(Protocol):
def draw(self) -> None: ...
def area(self) -> float: ...
class Circle:
def __init__(self, r: float): self.r = r
def draw(self) -> None: print("O")
def area(self) -> float: return 3.14*self.r**2
class Square:
def __init__(self, s: float): self.s = s
def draw(self) -> None: print("[]")
def area(self) -> float: return self.s**2
render(Circle(5)) # works
render(Square(4)) # works
print(isinstance(Circle(1), Drawable)) # True — runtime_checkable
# Callable[[arg_types...], return_type]
def apply(func: Callable[[int], int], x: int) -> int:
return func(x)
m: Movie = {"title":"Inception","year":2010,"rating":8.8}
🔧 Running mypy
pip install mypy then mypy your_file.py or mypy --strict your_file.py. For gradual adoption,
annotate public APIs first and use # type: ignore sparingly.
✏ Practice Exercises
69. Take Chapter 5's BankAccount class and add full type annotations. Run mypy --strict
on it.
70. Write a typed generic class Queue[T] with enqueue, dequeue, peek, is_empty,
__len__.
71. Define a Protocol Comparable with __lt__ and __eq__. Write a generic typed_sort(lst:
list[C]) function.
72. Annotate a function that accepts a callback: transform(data: list[T], fn: Callable[[T], U])
-> list[U].
73. Create a TypedDict for a User and write load_users(path: Path) -> list[User] with full
type annotations.
Chapter 13
ABCs & Dataclasses
abc module, @dataclass, NamedTuple, field()
class Shape(ABC):
@abstractmethod
def area(self) -> float: ...
@abstractmethod
def perimeter(self) -> float: ...
class Circle(Shape):
def __init__(self, r: float): self.r = r
def area(self) -> float: return 3.14159 * self.r**2
def perimeter(self) -> float: return 2 * 3.14159 * self.r
class JSONSerializable(ABC):
@abstractmethod
def to_json(self) -> str: ...
13.3 @dataclass
from dataclasses import dataclass, field, asdict, astuple
@dataclass
class Point:
x: float
y: float = 0.0
def __post_init__(self):
object.__setattr__(self, "sort_index", [Link])
class Coordinate(NamedTuple):
lat: float
lon: float
altitude: float = 0.0
✏ Practice Exercises
74. Create an abstract Animal hierarchy with abstract speak(), move(), and concrete
describe(). Implement 4 subclasses.
75. Build a @dataclass Config with validation in __post_init__ that ensures all string fields
are non-empty.
76. Use @dataclass(frozen=True) for a Money class. Implement __add__, __mul__.
Money objects should be hashable.
77. Create a NamedTuple Version(major, minor, patch) with __str__ returning "v1.2.3"
and __lt__ for comparisons.
78. Write an abstract Repository[T] ABC with get, save, delete, list_all. Implement
InMemoryRepository[T].
Chapter 14
Metaclasses & Descriptors
__slots__, descriptors, __init_subclass__, metaclasses
These are the advanced OOP tools used to build frameworks, ORMs, and APIs in Python.
Django's Model and SQLAlchemy's Column both use these internally.
14.1 __slots__
class WithDict:
def __init__(self, x, y):
self.x, self.y = x, y
class WithSlots:
__slots__ = ("x","y") # no __dict__, fixed attributes
def __init__(self, x, y):
self.x, self.y = x, y
import sys
a = WithDict(1,2); b = WithSlots(1,2)
print([Link](a.__dict__)) # ~200 bytes
# b has no __dict__ — ~50-60 bytes, faster attribute access
14.2 Descriptors
class Validator:
"""Non-data descriptor (read-only)."""
def __set_name__(self, owner, name):
[Link] = name
class Circle:
radius = PositiveNumber()
def __init__(self, r): [Link] = r
c = Circle(5)
print([Link]) # 5
# Circle(-1) → ValueError
14.3 __init_subclass__
class Plugin:
registry: dict[str,type] = {}
print([Link])
# {"json":<class JsonPlugin>, "csv":<class CsvPlugin>}
14.4 Metaclasses
# type is the metaclass of all classes
print(type(int)) # <class 'type'>
print(type(list)) # <class 'type'>
# Custom metaclass
class Singleton(type):
_instances: dict = {}
class Database(metaclass=Singleton):
def __init__(self): [Link] = False
db1 = Database()
db2 = Database()
print(db1 is db2) # True
@staticmethod
def _wrap(func):
from functools import wraps
@wraps(func)
def wrapper(*a,**kw):
print(f"Calling {func.__name__}")
return func(*a,**kw)
return wrapper
✏ Practice Exercises
79. Rewrite the BankAccount class using __slots__. Benchmark memory usage vs a
regular class for 1,000,000 instances.
80. Write a TypeEnforced descriptor that validates assigned values against a specified
type.
81. Use __init_subclass__ to build a command registry: any subclass of Command auto-
registers itself by name.
82. Implement a Singleton metaclass. Verify with tests that only one instance is ever
created per class.
83. Build a simple ORM-style Row metaclass: class User(Row): id=IntField();
name=StrField() auto-maps to dict.
Chapter 15
Async / Await & asyncio
Coroutines, event loop, tasks, streams, semaphores
asyncio enables concurrent I/O without threads. It is the foundation of async web frameworks
(FastAPI, aiohttp), async database clients, and high-performance servers.
[Link](main())
[Link](main())
# Cancel a task
[Link]()
try:
await t2
except [Link]:
print("t2 was cancelled")
# [Link] — producer/consumer
async def producer(q):
for i in range(5):
await [Link](i)
await [Link](0.1)
await [Link](None) # sentinel
# Async generator
async def ticker(delay, to):
for i in range(to):
yield i
await [Link](delay)
✏ Practice Exercises
84. Write an async function that fetches 10 URLs concurrently using [Link]() with
[Link]() as mock.
85. Implement an async producer-consumer queue where 3 producers and 2 consumers
run concurrently.
86. Write an async retry decorator: retries the coroutine up to N times on exception with
exponential backoff.
87. Create an async context manager that logs the start/end time of any async block.
88. Build an async rate limiter: allows at most N calls per second using
[Link] and [Link].
Chapter 16
Concurrency — Threading, Multiprocessing
& the GIL
When to use which, GIL explained, [Link]
16.2 threading
import threading, time
# Thread-safe counter
lock = [Link]()
counter = 0
def inc():
global counter
for _ in range(100_000):
with lock:
counter += 1
ts = [[Link](target=inc) for _ in range(4)]
for t in ts: [Link]()
for t in ts: [Link]()
print(counter) # 400000
16.3 multiprocessing
from multiprocessing import Process, Pool, Queue, Manager
import os
def cpu_task(n):
return sum(i*i for i in range(n))
# Shared state
def counter_worker(shared_count, lock):
for _ in range(1000):
with lock:
shared_count.value += 1
✏ Practice Exercises
89. Write a program that demonstrates the GIL: two CPU-bound threads vs two
processes — time both.
90. Build a thread-safe LRU cache using [Link] and [Link].
91. Implement a parallel file processor: use ProcessPoolExecutor to apply a
transformation to every file in a directory.
92. Create a producer-consumer pipeline using [Link] with 2 producers and 3
consumers.
93. Write an I/O-bound benchmark: download 20 URLs using (a) sequential, (b)
threading, (c) asyncio — compare times.
PART III
Professional Python
Memory, testing, packaging, patterns & projects
Chapter 17
Memory Model & Garbage Collection
Reference counting, cyclic GC, weakref, __del__
# Reference counting
x = [1, 2, 3]
print([Link](x)) # 2 (x + argument to getrefcount)
y = x # refcount → 3
del y # refcount → 2
del x # refcount → 0 → freed
# Object size
print([Link]([])) # ~56 bytes
print([Link]([1]*1000)) # ~8056 bytes
# Cyclic GC
import gc
[Link]() # manual collection
print(gc.get_threshold()) # (700, 10, 10) — generation thresholds
[Link]() # disable (use with caution)
a = Node(1)
b = Node(2)
[Link] = b
[Link] = a # cycle — leaks without gc
del a, b
[Link]() # cyclic gc detects and frees them
17.3 weakref
import weakref
class Cache:
def __init__(self): self._store = {}
class BigData:
def __init__(self, n): [Link] = list(range(n))
cache = Cache()
data = BigData(1000)
[Link]("big", data)
print([Link]("big")) # <BigData ...>
del data
print([Link]("big")) # None — object freed
def __del__(self):
print(f" {[Link]}: released")
r = Resource("DB")
del r # immediately: "DB: released"
# context manager is better than __del__ for cleanup
from contextlib import contextmanager
@contextmanager
def resource(name):
print(f"{name}: acquired")
try: yield
finally: print(f"{name}: released")
✏ Practice Exercises
94. Write a memory_usage() function using [Link]() that deep-counts nested
containers.
95. Create a circular reference with two objects. Confirm [Link]() frees them by
tracking object count.
96. Build a WeakValueDictionary cache that automatically evicts entries when objects are
garbage collected.
97. Profile memory usage of a @dataclass vs a regular class vs __slots__ for 1,000,000
instances.
98. Implement an object pool using weakref that reuses objects instead of creating new
ones.
Chapter 18
Testing with pytest
pytest, fixtures, parametrize, mocking, coverage
Writing tests is as important as writing code. pytest is the de-facto Python testing framework —
expressive, extensible, and powerful.
def test_divide():
assert divide(10, 2) == 5.0
def test_divide_by_zero():
import pytest
with [Link](ZeroDivisionError):
divide(1, 0)
18.2 Fixtures
import pytest
from pathlib import Path
@[Link]
def sample_data():
return [1, 2, 3, 4, 5]
@[Link]
def tmp_file(tmp_path):
f = tmp_path / "[Link]"
f.write_text("hello")
return f
def test_sum(sample_data):
assert sum(sample_data) == 15
def test_file_content(tmp_file):
assert tmp_file.read_text() == "hello"
# Scoped fixtures
@[Link](scope="module") # created once per module
def db_connection():
conn = create_connection()
yield conn
[Link]()
18.3 Parametrize
import pytest
@[Link]("a,b,expected", [
(1, 2, 3),
(-1, 1, 0),
(0, 0, 0),
(100, -50, 50),
(0.1, 0.2, [Link](0.3)), # float comparison
("hello", " world", "hello world"),
([1,2], [3,4], [1,2,3,4]),
(True, False, 1),
])
def test_add_parametrized(a, b, expected):
assert add(a, b) == expected
# Multiple decorators
@[Link]("n", [1,2,3])
@[Link]("multiplier", [2,3])
def test_multiply(n, multiplier):
assert n * multiplier == n * multiplier
18.4 Mocking
from [Link] import Mock, MagicMock, patch, call
# Basic mock
m = Mock()
m.some_method.return_value = 42
print(m.some_method()) # 42
m.some_method.assert_called_once()
def notify_user(user_id):
send_email(f"{user_id}@[Link]","Welcome!")
def test_notify():
with patch("__main__.send_email") as mock_email:
notify_user(42)
mock_email.assert_called_once_with("42@[Link]","Welcome!")
# patch as decorator
@patch("[Link]")
def test_api_call(mock_get):
mock_get.return_value.json.return_value = {"status":"ok"}
result = call_api("[Link]
assert result["status"] == "ok"
# Custom markers
@[Link]
@[Link]
def test_database_roundtrip(): ...
# [Link] or [Link]
# [pytest]
# markers =
# slow: marks tests as slow
# integration: requires external services
# Skip / xfail
@[Link](reason="WIP")
def test_not_ready(): ...
✏ Practice Exercises
99. Write a full test suite for a Stack class: push, pop, peek, is_empty, overflow handling.
100. Use @[Link] to test a Caesar cipher with 10 different inputs
and expected outputs.
101. Write a test that mocks the file system: test a function that reads, processes,
and writes a file.
102. Create fixtures for a temporary SQLite database. Write 5 tests that use it with
scope="function".
103. Write a property-based test using the hypothesis library: test that
reverse(reverse(lst)) == lst for all lists.
Chapter 19
Packaging & Distribution
[Link], setuptools, building & publishing to PyPI
19.2 [Link]
# [Link]
[build-system]
requires = ["setuptools>=68", "wheel"]
build-backend = "[Link]:build"
[project]
name = "mypackage"
version = "1.0.0"
description = "A great Python package"
readme = "[Link]"
license = {file = "LICENSE"}
requires-python = ">=3.11"
keywords = ["example","package"]
authors = [
{name = "Alice Smith", email = "alice@[Link]"},
]
dependencies = [
"requests>=2.31",
"click>=8.0",
]
[[Link]-dependencies]
dev = ["pytest","mypy","ruff"]
[[Link]]
my-tool = "[Link]:main" # creates CLI command
[[Link]]
strict = true
[[Link]]
line-length = 88
# 2. Build distributions
# python -m build
# → dist/
# [Link]
# [Link]
✏ Practice Exercises
104. Create a full Python package with [Link], src layout, and a CLI entry
point using click.
105. Add mypy and ruff to your package's dev dependencies and create a Makefile
with lint, type-check, test targets.
106. Write a [Link] for your package using the Keep a Changelog
format.
107. Set up GitHub Actions CI: run tests on Python 3.11 and 3.12 on push and PR.
108. Publish your package to TestPyPI and install it in a fresh virtual environment
to verify it works.
Chapter 20
Design Patterns in Python
Pythonic implementations of classic patterns
Classic GoF patterns often look very different in Python — many are simplified by first-class
functions, decorators, and duck typing.
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
return cls._instance
Factory
from abc import ABC, abstractmethod
class Serializer(ABC):
@abstractmethod
def serialize(self, data: dict) -> str: ...
class JsonSerializer(Serializer):
def serialize(self, data): import json; return [Link](data)
class CsvSerializer(Serializer):
def serialize(self, data):
return ",".join(str(v) for v in [Link]())
Builder
class QueryBuilder:
def __init__(self): self._parts = {}
def build(self):
t = self._parts.get("table","?")
s = ",".join(self._parts.get("select",("*",)))
w = " AND ".join(self._parts.get("where",[]))
l = self._parts.get("limit","")
q = f"SELECT {s} FROM {t}"
if w: q += f" WHERE {w}"
if l: q += f" LIMIT {l}"
return q
q = (QueryBuilder().table("users").select("id","name")
.where("age > 18").where("active = 1").limit(10).build())
print(q)
class Coffee(ABC):
@abstractmethod
def cost(self) -> float: ...
@abstractmethod
def description(self) -> str: ...
class SimpleCoffee(Coffee):
def cost(self) -> float: return 1.0
def description(self) -> str: return "Coffee"
class MilkDecorator(Coffee):
def __init__(self, coffee: Coffee): self._c = coffee
def cost(self) -> float: return self._c.cost() + 0.5
def description(self) -> str: return self._c.description() + ", Milk"
class SugarDecorator(Coffee):
def __init__(self, coffee: Coffee): self._c = coffee
def cost(self) -> float: return self._c.cost() + 0.25
def description(self) -> str: return self._c.description() + ", Sugar"
c = SugarDecorator(MilkDecorator(SimpleCoffee()))
print([Link](), [Link]()) # Coffee, Milk, Sugar 1.75
class NewAPIAdapter:
def __init__(self, old): self._old = old
def fetch(self, resource_id): return self._old.get_data(resource_id)
class EventEmitter:
def __init__(self):
self._handlers: dict[str,list[Callable]] = defaultdict(list)
emitter = EventEmitter()
[Link]("login", lambda user: print(f"Welcome {user}!"))
[Link]("login", lambda user: log_event("login", user))
[Link]("login", "Alice")
Strategy
from typing import Callable
names = ["Charlie","Alice","Bob"]
print(sort_data(names, asc))
print(sort_data(names, by_length))
Command Pattern
from dataclasses import dataclass, field
from typing import Protocol
class Command(Protocol):
def execute(self) -> None: ...
def undo(self) -> None: ...
@dataclass
class WriteText:
doc: list
text: str
_prev: str = field(init=False)
def execute(self):
self._prev = [Link][-1] if [Link] else ""
[Link]([Link])
def undo(self):
[Link]()
class Editor:
def __init__(self):
[Link], [Link] = [], []
def undo(self):
if [Link]:
[Link]().undo()
✏ Practice Exercises
109. Implement a plugin system using the Factory pattern and __init_subclass__
auto-registration.
110. Build an event-driven order system: Order emits
"placed","shipped","delivered" events; handlers log and notify.
111. Implement undo/redo for a text editor using the Command pattern. Support at
least 10 commands.
112. Write a caching Proxy for any callable that stores results by argument hash.
113. Create a Builder for constructing HTTP requests with method, url, headers,
query params, body fluent API.
Chapter 21
Performance & Profiling
timeit, cProfile, line_profiler, tips & tricks
# timeit — microbenchmark
t1 = [Link]("sum(range(1000))", number=10000)
t2 = [Link]("[x for x in range(1000)]", number=10000)
print(f"sum: {t1:.3f}s list: {t2:.3f}s")
21.2 cProfile
import cProfile, pstats, io
pr = [Link]()
[Link]()
# ... code to profile ...
[Link]()
s = [Link]()
ps = [Link](pr, stream=s).sort_stats("cumulative")
ps.print_stats(20)
print([Link]())
# Command line
# python -m cProfile -o [Link] [Link]
# python -m pstats [Link]
# 5. [Link]() not +=
parts = ["a","b","c","d"]
result = "".join(parts) # not: r=""; for p in parts: r+=p
✏ Practice Exercises
114. Profile a function that builds a frequency table three ways: dict loop,
defaultdict, Counter. Show timings.
115. Benchmark string concatenation (+= vs join) for 10,000 and 1,000,000 strings.
116. Use cProfile to find the bottleneck in a recursive Fibonacci implementation.
Optimise with lru_cache.
117. Compare memory usage of list vs generator vs array for 1,000,000 floats
using [Link] and tracemalloc.
118. Write a benchmark comparing Python dict lookup vs list search at various
sizes — explain the Big-O difference.
Chapter 22
Advanced Standard Library
logging, argparse, contextlib, functools, operator
22.1 logging
import logging
# Basic config
[Link](
level=[Link],
format="%(asctime)s %(name)s %(levelname)s %(message)s",
datefmt="%H:%M:%S",
handlers=[
[Link](),
[Link]("[Link]"),
]
)
log = [Link](__name__)
[Link]("detail")
[Link]("normal operation")
[Link]("unexpected but handled")
[Link]("something failed")
[Link]("system cannot continue")
# Structured logging
[Link]("User logged in", extra={"user_id":42, "ip":"[Link]"})
# Log exceptions
try:
1/0
except ZeroDivisionError:
[Link]("Math error") # includes traceback
22.2 argparse
import argparse, sys
def build_parser():
parser = [Link](
description="Process CSV files",
formatter_class=[Link],
)
parser.add_argument("input", type=str, help="Input CSV path")
parser.add_argument("-o","--output", default="[Link]")
parser.add_argument("-n","--limit", type=int, default=None)
parser.add_argument("--verbose", action="store_true")
parser.add_argument("--format", choices=["csv","json","tsv"])
parser.add_argument("--columns", nargs="+")
return parser
def main():
args = build_parser().parse_args()
if [Link]:
[Link](level=[Link])
process([Link], [Link], [Link])
if __name__ == "__main__":
main()
@[Link](int)
def _(n): return n * 2
@[Link](str)
def _(s): return [Link]()
@[Link](list)
def _(lst): return [process(x) for x in lst]
✏ Practice Exercises
119. Build a logging setup with two handlers: DEBUG to file, WARNING+ to
console. Use RotatingFileHandler.
120. Write a CLI tool with argparse: subcommands (add, remove, list) for a todo list
stored in JSON.
121. Use singledispatch to write a serialize() function that handles int, float, str, list,
dict differently.
122. Use total_ordering on a Version(major,minor,patch) class and test all 6
comparison operators.
123. Write a retry context manager using ExitStack that retries the block N times on
specified exceptions.
Chapter 23
Advanced Patterns & Idioms
Context variables, __class_getitem__, slots+descriptors combo
IntList = TypedList[int]
lst = IntList()
[Link](1) # OK
[Link]("x") # TypeError
@classmethod
def for_format(cls, fmt: str) -> "Exporter":
klass = cls._registry.get(fmt)
if not klass: raise ValueError(f"No exporter for {fmt!r}")
return klass()
e = Exporter.for_format("json")
print([Link]({"a":1}))
class Field(Generic[T]):
"""Descriptor that validates type on assignment."""
class User:
name = Field(str, nullable=False)
age = Field(int, default=0)
email = Field(str)
✏ Practice Exercises
124. Use contextvars to implement a request-scoped logger that prefixes all
messages with the current request ID.
125. Build a validated Schema system using descriptors: IntField, StrField,
FloatField with min/max constraints.
126. Implement a generic Pair[T, U] class using __class_getitem__ that supports
iteration and unpacking.
127. Create a full plugin architecture with Exporter/Importer base classes, auto-
registration, and a CLI frontend.
Chapter 24
Capstone Projects
Full projects combining all concepts
These four projects are designed to be completed over several days each. They combine
concepts from all three parts of the book.
# Target usage:
# @pipeline
# def etl(source=CSVSource("[Link]"), sink=JSONSink("[Link]")):
# return (
# Filter(lambda r: r["age"] > 18),
# Map(lambda r: {**r, "name": r["name"].title()}),
# )
What to do next
You have now covered everything from basic variables to metaclasses, async programming,
testing, and design patterns. Here is a suggested path forward:
• Web Development: FastAPI (async REST APIs), Django (full-stack), SQLAlchemy (ORM)
• Data Science: numpy, pandas, matplotlib, scikit-learn, Jupyter
• AI & ML: PyTorch, TensorFlow, Hugging Face transformers, LangChain
• DevOps: Docker, GitHub Actions CI/CD, Poetry, pre-commit hooks
• Systems: Cython (C extensions), ctypes, cffi, CFFI for interfacing with C libraries
Happy Coding! 🐍