Posts

Async Python: asyncio Crash Course

Part 14 of the Python for Everyone track. Last updated: September 2026. asyncio lets one Python process juggle thousands of I/O-bound tasks — API calls, database queries, file reads — without threads. The mental model is small: async def , await , and an event loop that switches tasks while they wait. Your first coroutine import asyncio async def greet(): # async def makes a COROUTINE function await asyncio.sleep(1) # await: pause here, let others run return "hello" async def main(): result = await greet() # await runs the coroutine to completion print(result) asyncio.run(main()) # entry point: runs the event loop asyncio.run() is the standard entry point — it creates the event loop, runs your coroutine, and closes everything cleanly. Concurrency with gather import asyncio, time async def fetch(site): await asyncio.sleep(1) # pretend this is a network call return f"data from {site...

Testing Python Code with pytest: A Beginner's Guide

Part 13 of the Python for Everyone track. Last updated: September 2026. pytest is the de facto standard for testing Python — simpler than unittest, no boilerplate classes required. If you write code that matters, test it; pytest makes the habit painless. Your first test # test_math_utils.py from math_utils import add def test_add_positive(): assert add(2, 3) == 5 def test_add_negative(): assert add(-1, -1) == -2 Run with pytest in the terminal. Test functions just use plain assert — pytest rewrites assertions to show beautiful failure diffs. Testing exceptions with pytest.raises import pytest from accounts import withdraw def test_withdraw_too_much(): with pytest.raises(ValueError, match="insufficient funds"): withdraw(100, 200) Fixtures: reusable setup import pytest @pytest.fixture def sample_user(): return {"name": "Ada", "age": 36} # fresh dict for EVERY test def test_user_name(sample_user): assert...

Python Decorators and Generators Explained

Part 12 of the Python for Everyone track. Last updated: September 2026. Decorators and generators are Python's two "wow, that's elegant" features. A decorator wraps a function to add behavior; a generator produces values lazily, one at a time. Both appear constantly in real code — Flask routes, pytest fixtures, and data pipelines. Decorators: functions that wrap functions import functools, time def timer(func): @functools.wraps(func) # preserves the original name/docstring def wrapper(*args, **kwargs): start = time.perf_counter() result = func(*args, **kwargs) print(f"{func.__name__} took {time.perf_counter() - start:.3f}s") return result return wrapper @timer # equivalent to: slow = timer(slow) def slow(): time.sleep(0.5) slow() # slow took 0.500s Real-world decorators you'll meet: @app.route (Flask), @pytest.fixture , @lru_cache (memoization), @property , ...

Python Exception Handling: try/except/raise

Part 11 of the Python for Everyone track. Last updated: September 2026. Exceptions aren't crashes to fear — they're Python's normal way of signaling "something unexpected happened." Master the four-block pattern and you'll write code that fails gracefully instead of mysteriously. try / except / else / finally try: age = int(input("Age: ")) # may raise ValueError print(f"Next year: {age + 1}") except ValueError: print("Please enter a number!") except (TypeError, OverflowError) as e: # catch multiple, keep the object print(f"Unexpected problem: {e}") else: print("No errors — runs only if try succeeded") # rare but clean finally: print("Always runs — cleanup goes here") Order matters: put specific exceptions before general ones, and never use a bare except: — it swallows KeyboardInterrupt and hides bugs. raise: throw your own def withdraw(balance, amount): ...

Python File Handling: Read, Write, JSON and CSV

Part 10 of the Python for Everyone track. Last updated: September 2026. File I/O in Python boils down to one rule: always use with open(...) . Everything else — text vs binary, JSON, CSV, paths — builds on that. Reading and writing text files # Writing (overwrites existing content) with open("notes.txt", "w") as f: f.write("Line one\n") f.write("Line two\n") # Appending with open("notes.txt", "a") as f: f.write("Line three\n") # Reading — three flavors with open("notes.txt") as f: whole = f.read() # one big string with open("notes.txt") as f: lines = f.readlines() # list of lines with open("notes.txt") as f: for line in f: # iterate directly — memory-friendly for big files print(line.strip()) The with block closes the file automatically, even if an error occurs. Manual f.close() is legacy style. Modes cheat sheet "...

Python Modules, Packages and pip Explained

Part 9 of the Python for Everyone track. Last updated: September 2026. Real Python programs are never one file. Modules split code across files, packages group modules into directories, and pip brings in the world's libraries. This post covers all three plus the standard library gems you should know. Importing: the four forms import math # use as math.sqrt(16) import math as m # alias: m.sqrt(16) from math import sqrt # import one name: sqrt(16) from math import sqrt as s # alias a single name Prefer import module over from module import * — star imports pollute your namespace and hide where names came from. Writing your own module A module is just a .py file . Create helpers.py : # helpers.py def shout(text): return text.upper() + "!" PI_APPROX = 3.14 Use it from another file in the same folder: # main.py import helpers print(helpers.shout("hello")) # HELLO! The __name__ == "__main__" idiom # hel...

Python OOP Part 2: Inheritance, Polymorphism and Dunder Methods

Part 8 of the Python for Everyone track. Last updated: September 2026. Inheritance in Python is simpler than in Java — no interfaces, no abstract boilerplate, just classes building on classes. Combined with dunder methods, your custom classes can behave exactly like built-ins. Inheritance and super() class Animal: def __init__(self, name): self.name = name def speak(self): return "..." class Dog(Animal): # Dog inherits from Animal def __init__(self, name, breed): super().__init__(name) # call the parent constructor self.breed = breed def speak(self): # override the parent method return f"{self.name} says woof!" d = Dog("Rex", "Labrador") print(d.speak()) # Rex says woof! print(isinstance(d, Animal)) # True — a Dog IS an Animal Polymorphism = duck typing class Cat(Animal): def speak(self): return f"{self.name}...