Modules & Packages
__name__ == "__main__" guard. Building packages with __init__.py. Virtual environments with venv. Installing third-party packages with pip. A tour of the most important standard library modules.
Creating & Importing Modules
A module is simply a Python file. Any .py file can be imported by other files. When you write import math, Python finds math.py (or a compiled equivalent) and executes it, making its names available under the math namespace.
# mathutils.py — a reusable math utilities module PI = 3.14159265358979 def circle_area(r): """Return area of a circle.""" return PI * r ** 2 def circle_circumference(r): return 2 * PI * r def is_prime(n): """Return True if n is prime.""" if n < 2: return False return all(n % i != 0 for i in range(2, int(n**.5)+1)) # __name__ guard: runs only when executed directly, not when imported if __name__ == "__main__": print("Testing mathutils...") print(circle_area(5)) # 78.539... print(is_prime(17)) # True
# Style 1: import the whole module (namespace preserved) import mathutils print(mathutils.circle_area(3)) # 28.274... print(mathutils.PI) # 3.14159... # Style 2: import specific names from mathutils import is_prime, PI print(is_prime(13)) # True — no mathutils. prefix needed # Style 3: alias (useful for long names) import mathutils as mu print(mu.circle_area(7)) # Style 4: from ... import * (generally avoid — pollutes namespace) # from mathutils import * # ── Standard library imports ────────────────── import math print(math.sqrt(144)) # 12.0 print(math.factorial(10)) # 3628800 import random print(random.choice(["Ali", "Sara", "Zara"])) print(random.randint(1, 100)) from datetime import datetime, timedelta now = datetime.now() print(now.strftime("%d %B %Y")) tomorrow = now + timedelta(days=1)
if __name__ == "__main__"?__name__ to the module's name (e.g. "mathutils"). When you run a file directly, __name__ is set to "__main__". This guard lets you write test/demo code in a module that only runs when you execute the file directly — not when it's imported. Every module you write should have this guard around runnable code.Packages — Organising Multiple Modules
A package is a folder containing an __init__.py file and multiple modules. This lets you organise large projects into namespaced sub-modules. The __init__.py runs when the package is imported and controls what the package exports.
# __init__.py — package initialisation # Import key names so users can write: from bwb_toolkit import validate_email from .validators import validate_email, validate_age from .formatters import format_currency, format_date __version__ = "1.0.0" __author__ = "BitWithBite" # __all__ controls what 'from package import *' exports __all__ = ["validate_email", "validate_age", "format_currency"]
# bwb_toolkit/validators.py import re def validate_email(email): """Return True if email is valid format.""" pattern = re.compile(r'^[\w.-]+@[\w.-]+\.\w{2,}$') return bool(pattern.match(email)) def validate_age(age): """Return True if age is 5–120.""" return isinstance(age, int) and 5 <= age <= 120 # main.py — importing from the package from bwb_toolkit import validate_email # from __init__ from bwb_toolkit.validators import validate_age # direct submodule from bwb_toolkit.data.csv_handler import load_csv # nested subpackage print(validate_email("ali@bwb.com")) # True print(validate_age(200)) # False
pip & Virtual Environments
pip is Python's package installer. It downloads and installs packages from PyPI (Python Package Index). A virtual environment is an isolated Python installation for each project — it keeps dependencies separate so Project A's packages don't conflict with Project B's.
# ── Create a virtual environment ───────────── python -m venv venv # creates venv/ folder # ── Activate it ────────────────────────────── # Windows: venv\Scripts\activate # macOS/Linux: source venv/bin/activate # ── Install packages ───────────────────────── pip install requests # HTTP library pip install pandas # data analysis pip install flask # web framework pip install python-dotenv # .env file support # ── Save dependencies ───────────────────────── pip freeze > requirements.txt # snapshot all installed packages # ── Install from requirements.txt ──────────── pip install -r requirements.txt # used by teammates or on server # ── Useful pip commands ─────────────────────── pip list # show installed packages pip show requests # details about a package pip install --upgrade pip # upgrade pip itself pip uninstall flask # remove a package # ── Deactivate ──────────────────────────────── deactivate
venv/ to your .gitignorerequirements.txt. Anyone cloning your project runs pip install -r requirements.txt to recreate the environment. Also add __pycache__/ and *.pyc to .gitignore.Standard Library — Must-Know Modules
Python ships with an enormous standard library — "batteries included." These are the modules every Python developer reaches for regularly:
from collections import Counter, defaultdict from functools import lru_cache from itertools import combinations import re, os # Counter — count occurrences in one line words = "python is easy and python is fun".split() counts = Counter(words) print(counts.most_common(3)) # [('python', 2), ('is', 2), ...] # defaultdict — no KeyError on missing keys groups = defaultdict(list) students= [("A", "Ali"), ("B", "Sara"), ("A", "Zara")] for grade, name in students: groups[grade].append(name) # no .setdefault() needed! print(dict(groups)) # {'A': ['Ali', 'Zara'], 'B': ['Sara']} # lru_cache — memoize expensive functions @lru_cache(maxsize=128) def fib(n): if n <= 1: return n return fib(n-1) + fib(n-2) print(fib(50)) # 12586269025 — instant with caching # re — regex text = "Call us at 0300-1234567 or 042-3456789" nums = re.findall(r'\d[\d-]+', text) print(nums) # ['0300-1234567', '042-3456789'] # os — environment and paths print(os.getcwd()) print(os.getenv("HOME", "not set")) # combinations teams = list(combinations(["Ali","Sara","Zara","Omar"], 2)) print(teams) # all 6 pairs
if __name__ == "__main__" guard do?bwb_utils/ package with 3 modules and a proper __init__.py:bwb_utils/strings.py — functions: title_case(text), count_words(text), is_palindrome(text) (ignore case/spaces), slugify(text) (lowercase, spaces→hyphens, remove non-alnum)bwb_utils/numbers.py — functions: clamp(n, lo, hi), percentage(part, total), primes_up_to(n) using Sieve of Eratosthenesbwb_utils/date_utils.py — functions: days_until(date_str) (days until a "YYYY-MM-DD" date), format_relative(date_str) returns "X days ago" or "in X days"__init__.py — import key functions, set __version__ = "1.0.0". Write a main.py that imports from the package and tests all functions.
💡 Show hints
- slugify:
re.sub(r'[^a-z0-9-]', '', text.lower().replace(' ', '-')) - Sieve: start with list of True, mark multiples as False
- days_until:
(datetime.strptime(date_str, "%Y-%m-%d").date() - date.today()).days - Relative:
f"in {d} days"if positive,f"{-d} days ago"if negative
# ── bwb_utils/strings.py ────────────────────── import re def title_case(text): return text.title() def count_words(text): return len(text.split()) def is_palindrome(text): s = re.sub(r'[^a-z0-9]', '', text.lower()) return s == s[::-1] def slugify(text): return re.sub(r'[^a-z0-9-]', '', text.lower().replace(' ', '-')) # ── bwb_utils/numbers.py ───────────────────── def clamp(n, lo, hi): return max(lo, min(n, hi)) def percentage(part, total): return (part/total*100) if total else 0 def primes_up_to(n): sieve = [True] * (n+1) sieve[0] = sieve[1] = False for i in range(2, int(n**.5)+1): if sieve[i]: sieve[i*i::i] = [False]*len(sieve[i*i::i]) return [i for i, p in enumerate(sieve) if p] # ── bwb_utils/date_utils.py ─────────────────── from datetime import datetime, date def days_until(date_str): target = datetime.strptime(date_str, "%Y-%m-%d").date() return (target - date.today()).days def format_relative(date_str): d = days_until(date_str) if d == 0: return "today" elif d > 0: return f"in {d} days" else: return f"{-d} days ago" # ── bwb_utils/__init__.py ───────────────────── from .strings import title_case, slugify, is_palindrome from .numbers import clamp, primes_up_to from .date_utils import format_relative __version__ = "1.0.0" # ── main.py ─────────────────────────────────── import bwb_utils as bwb print(bwb.slugify("Hello BitWithBite 2025!")) # hello-bitwithbite-2025 print(bwb.is_palindrome("A man a plan a canal Panama")) # True print(bwb.primes_up_to(30)) # [2,3,5,7,11,13,17,19,23,29] print(bwb.clamp(150, 0, 100)) # 100 print(bwb.format_relative("2026-01-01")) # e.g. "in 225 days"