Sitemap

Python Modules, Dates, and Error Handling

7 min readSep 26, 2026

--

The morning I forgot pip and a venv were different things

Weeks into practicing Python fundamentals, I thought I had a decent handle on things. Then I opened a notebook, typed python -m venv myenv into a code cell, and stared at a SyntaxError I genuinely didn't understand at first. It took me an embarrassing few minutes to realize I was trying to run a terminal command as if it were Python code. Small mistake, but it exposed a gap: I could write loops and functions just fine, but I hadn't actually internalized the difference between "code that runs inside Python" and "commands that manage Python from the outside." That mix-up, and the string of Colab-specific errors that followed it, ended up teaching me more than the exercises themselves. This article walks through that second stretch of practice: modules, dates, JSON, error handling, and virtual environments, along with the mistakes that made each concept actually stick.

Why this batch of practice mattered

The first week was about control flow — loops, conditionals, functions, basic data structures. This second set of tasks moved into something different: working with Python’s modules, the built-in and third-party tools that do the heavy lifting so you don’t reinvent things like date math or JSON parsing yourself. This is also where pip and virtual environments show up, since almost no real Python project runs on the standard library alone.

Working with the math module

Square root and factorial

import math
print(math.sqrt(144))       # 12.0
print(math.factorial(6)) # 720

math.sqrt() always returns a float, even when the answer is a whole number — worth remembering if you're ever comparing it directly to an integer. math.factorial() multiplies every whole number from 1 up to the number given, which is exactly the loop-based factorial program from week one, just handled for you.

Rounding with math

import math
number = 5.768
print(math.floor(number * 100) / 100) # 5.76, truncates
print(round(number, 2)) # 5.77, rounds properly

This one caught me off guard. I assumed math would have an obvious "round to 2 decimals" function, but math.floor() and math.ceil() only truncate in one direction — they don't round to the nearest value. Python's built-in round(), which isn't part of the math module at all, is actually the right tool for normal rounding. Small distinction, easy to miss.

GCD, and π

import math
num1 = int(input("Enter first number: "))
num2 = int(input("Enter second number: "))
print(math.gcd(num1, num2))
import math
radius = 5
area = math.pi * radius ** 2

math.gcd() finds the greatest common divisor without you having to write the Euclidean algorithm by hand, and math.pi gives π to enough decimal places for basically any geometry calculation you'd need.

Dates without the guesswork

Getting today’s date and time

from datetime import datetime, date
now = datetime.now()          # date and time
today = date.today() # date only, no time

I’d been using datetime.now() for everything until I realized date.today() exists specifically for when you don't care about the clock time — which, it turns out, is most of the time.

Formatting dates

from datetime import datetime
print(datetime.now().strftime("%d-%m-%Y"))

strftime() (string format time) is the function that turns a date object into whatever text format you want. %d, %m, and %Y stand for day, month, and four-digit year — get the order of those right and the rest is just picking your separators.

Age from birth year, and days until a birthday

from datetime import datetime, date
birth_year = int(input("Enter your birth year: "))
age = datetime.now().year - birth_year
def days_until_birthday(birth_date_str):
birth_date = date.fromisoformat(birth_date_str)
today = date.today()
next_birthday = birth_date.replace(year=today.year)
if next_birthday < today:
next_birthday = next_birthday.replace(year=today.year + 1)
return (next_birthday - today).days

The birthday calculator is the one that actually taught me something about date logic: you can’t just subtract two dates and assume the answer is positive. If someone’s birthday already passed this year, you have to roll it forward to next year before subtracting, or you’ll get a negative number of days. .replace(year=...) swaps out just the year on an existing date, which is the cleanest way to do that.

Days between two arbitrary dates

from datetime import date
date1 = date.fromisoformat(input("Enter first date (YYYY-MM-DD): "))
date2 = date.fromisoformat(input("Enter second date (YYYY-MM-DD): "))
print(abs((date2 - date1).days))

Wrapping the subtraction in abs() means it doesn't matter which date the user enters first — you always get a positive count.

JSON: turning Python objects into text and back

import json
students = [
{"name": "Alice", "age": 20, "grade": "A"},
{"name": "Bob", "age": 22, "grade": "B"},
]
with open("students.json", "w") as f:
json.dump(students, f, indent=4)
with open("students.json", "r") as f:
loaded = json.load(f)

The naming took me a moment to lock in: json.dumps() converts a Python object to a JSON string (the "s" is for string), while json.dump() writes it directly to a file. Same relationship on the reading side — json.loads() for a string, json.load() for a file. Mixing these up gives confusing errors that don't obviously point back to "wrong function."

Get Babatundenaheemot’s stories in your inbox

Join Medium for free to get updates from this writer.

One thing worth knowing: JSON has no concept of a tuple. Feed json.dumps() a tuple and it comes back as a JSON array (which looks identical to a list) — Python's tuple/list distinction just doesn't exist on the other side.

And when a JSON file has a syntax problem — a trailing comma, a missing quote — json.load() doesn't fail silently. It raises a json.JSONDecodeError, which is worth catching explicitly if you're reading files you didn't generate yourself.

try:
with open("bad_file.json") as f:
data = json.load(f)
except json.JSONDecodeError:
print("The file contains invalid JSON.")

Error handling: catching problems before they crash the program

The basics

try:
result = 10 / 0
except ZeroDivisionError:
print("Cannot divide by zero.")
try:
value = int("abc")
except ValueError:
print("Cannot convert 'abc' to an integer.")

The pattern is always the same: wrap the risky line in try, and catch the specific exception it might raise. Catching ZeroDivisionError and ValueError separately (rather than one bare except:) means you actually know which problem occurred, instead of just knowing "something" went wrong.

Looping until the input is actually valid

while True:
try:
num = int(input("Enter an integer: "))
break
except ValueError:
print("Invalid input. Please enter a valid integer.")

This is the pattern I use now for basically any user-input program: wrap the input inside a while True loop, and only break out of it once the conversion succeeds. It's the difference between a program that crashes on the first typo and one that just asks again.

Bringing it together: a math quiz

import random
score = 0
for i in range(5):
num1, num2 = random.randint(1, 20), random.randint(1, 20)
op = random.choice(['+', '-', '*'])
answer = eval(f"{num1}{op}{num2}")
try:
guess = int(input(f"{num1} {op} {num2} = "))
if guess == answer:
score += 1
except ValueError:
print("Skipped — invalid input.")

This one combines almost everything from the two weeks — loops, conditionals, the random module, and try/except — into a single small program, which is really where these concepts start to feel less like isolated tasks and more like how you'd actually write something.

Where pip and virtual environments tripped me up

This was the messiest part of the whole practice set, and entirely because of where I was running things, not what I was typing.

python -m venv myenv
source myenv/bin/activate # macOS/Linux
myenv\Scripts\activate # Windows
pip install requests

That’s the standard flow on a real machine. But I was working in Google Colab, and Colab’s Python setup doesn’t ensurepip cleanly inside a fresh venv, so the environment got created with no pip inside it — myenv/bin/pip: No such file or directory. The fix was creating the venv without pip, then bootstrapping pip manually:

!python3 -m venv myenv --without-pip
!curl -sS https://bootstrap.pypa.io/get-pip.py -o get-pip.py
!myenv/bin/python3 get-pip.py
!myenv/bin/pip install numpy
!myenv/bin/pip list

The deeper lesson wasn’t really about Colab quirks — it was that a virtual environment exists specifically to keep one project’s installed packages separate from another’s, so two projects needing different versions of the same library don’t collide. Colab already runs each notebook in its own isolated container, which is exactly why creating a second isolated environment inside it behaves a little differently than on a normal machine.

Module vs. package, and other things I had to actually look up

A few conceptual questions from this set were less about writing code and more about vocabulary I’d been using loosely:

  • A module is a single .py file; a package is a folder of modules bundled together (numpy is a package, math is a module).
  • import math requires prefixing everything (math.sqrt(16)); from math import sqrt lets you drop the prefix (sqrt(16)) but risks name clashes if two modules define the same name.
  • pip install package==1.2.3 installs an exact version, which matters more than it sounds once you're trying to reproduce someone else's environment.

None of these are complicated once you say them out loud, but I’d been treating “module” and “package” as interchangeable for weeks before this exercise forced me to actually check.

What I’d tell someone hitting these same errors

Don’t skip past the SyntaxError or the No such file or directory message assuming it means you did the task wrong. More often than not with this batch, the concept was fine and the environment was the problem — Jupyter running shell commands as Python, or Colab handling venvs differently than a laptop would. Read the actual error text before assuming you misunderstood the exercise.

The other thing: math, datetime, and json all feel like small, almost boring modules to practice on their own. But date math, JSON files, and safe error handling are exactly what shows up once you're reading a real dataset or building anything that takes user input seriously — this is the unglamorous plumbing that everything else sits on top of.

--

--