Python: Module vs Package vs Library vs Framework, What’s the Difference?

The python module vs package vs library question comes up the moment you read any Python documentation. These four words (module, package, library, framework) get thrown around like they all mean the same thing. They do not. Each one means something specific, and once you can tell them apart you read docs faster and explain your own projects clearly.

“There are only two hard things in Computer Science: cache invalidation and naming things.”

Phil Karlton

Last Updated: July 2026 | Tested on: Python 3.14.6 | Difficulty: Intermediate | Reading Time: 14 minutes

“Just install the NumPy module.” “Import the Pandas library.” “Flask is a Python package.” You hear sentences like these every day, and they treat the four words as if they were the same. They are not. A module is one Python file. A package is a folder of modules with an __init__.py inside it. A library is the casual word for anything you pip install. And a framework is the odd one out: instead of you calling it, it calls your code.

Think of it like a kitchen. A single recipe card is a module. A recipe binder full of cards is a package. A whole shelf of binders you bought as a set is a library. And a meal-kit service that tells you what to cook and when? That is a framework: it runs the show, you just fill in the steps. This post lines up all four side by side, with real code you can run, so you stop guessing which word to use.

The 30-Second Decision Guide

Most Python module vs package confusion clears up with three quick questions, asked in order:

Two Meanings of ‘Package’Key DifferencePython Terminology Hierarchy (Smallest to Largest)Packages containmodulesLibraries containpackagesFrameworks arelibrariesthat control YOURcodeMODULE: Single .py filee.g., math.py, os.pyPACKAGE: Directory +__init__.pye.g., numpy/, pandas/LIBRARY: Distributedcollectione.g., requests,beautifulsoup4FRAMEWORK: Inversion ofcontrole.g., Django, Flask, FastAPIYou calla libraryA frameworkcalls youImport PackageDirectory youimport fromimport numpyDistribution PackageWhat you pip installpip install numpyPython Module vs Package vs Library: The Hierarchy from File to Framework

Tip: click the diagram to open it full screen, then use the zoom and pan controls for a closer look.

Read the diagram from the bottom up and the hierarchy clicks. A module is a single .py file. A package is a folder of modules. A library is a collection of packages you install as one bundle. A framework is a library that also dictates how your project is shaped and run. Each level sits on top of the one below it. The reason this matters: tutorials swap “library” and “package” as if they were the same word, and that is exactly where beginners get lost. Knowing which is which pays off the day you publish your own code to PyPI (the Python Package Index).

  • Is it a single .py file? → Module
  • Is it a directory with __init__.py? → Package
  • Did you pip install it? → Library (informal) or Distribution Package (formal)
  • Does it call YOUR code (inversion of control)? → Framework

Python Module vs Package vs Library: The Comparison Table

TermDefinitionExampleHow You Get It
ModuleSingle .py filemath, os, your utils.pyimport math
PackageDirectory with __init__.py, contains modulesjson (std lib), numpy/import numpy
LibraryInformal: collection of packages/modules distributed togetherrequests, beautifulsoup4, Pillowpip install requests
FrameworkLibrary with inversion of control: it calls your codeDjango, Flask, FastAPI, pytestpip install django

Module: The Atomic Unit

A module is the smallest piece in the whole story. One file, one namespace. Picture a single recipe card: it holds a handful of related things and nothing more. When you write import random, Python finds one file named random.py and hands you everything inside it.

📄 module_example.py: every .py file is a module

# random is a module: a single .py file in the standard library
import random
print(f"random is a: {type(random)}")
print(f"File: {random.__file__}")

# A plain module has __file__ (one file) but no __path__ (no folder)
print(f"Has __path__? {hasattr(random, '__path__')}")

# Your own file is a module too.
# Save helpers.py next to your script and import it as: import helpers

▶ Output

random is a: <class 'module'>
File: C:\Users\Rahul\AppData\Local\Programs\Python\Python314\Lib\random.py
Has __path__? False

What happened here: Python found one file, random.py, loaded it, and pointed the name random at it. The __file__ attribute is the full path to that single file. (Your path will look different from mine. It depends on your operating system and where Python is installed, so do not worry if it is not an exact match.) The important part is the last line: a plain module has __file__ but no __path__, because there is no folder to point at.

Hold onto that detail. It is the exact line that separates a module from a package in the next section. One small note: some standard library modules such as math are built straight into the interpreter, so they have no __file__ at all. That is why we used random here, which is a real file on disk you can open and read.

Package: A Directory of Modules

A package is a folder of modules with a file named __init__.py inside it. That folder is the heart of the Python module vs package distinction: one file versus a directory of files. If a module is one recipe card, a package is the binder that holds many cards together under one name. When you import json, you are importing a whole folder, and Python knows it is a package because of that __path__ attribute we just talked about.

📄 package_example.py: packages hold multiple modules

import json
import email

# json is a package: a folder with an __init__.py inside
print(f"json type: {type(json)}")
print(f"json path: {json.__path__}")  # Packages have __path__, modules do not

# email is also a package
print(f"email path: {email.__path__}")

# Packages can hold sub-modules you reach with a dot
from email import mime
from json import decoder
print(f"json.decoder: {decoder.__name__}")

▶ Output

json type: <class 'module'>
json path: ['C:\\Users\\Rahul\\AppData\\Local\\Programs\\Python\\Python314\\Lib\\json']
email path: ['C:\\Users\\Rahul\\AppData\\Local\\Programs\\Python\\Python314\\Lib\\email']
json.decoder: json.decoder

What happened here: Surprise: type(json) still says module, not package. That is not a bug. In Python’s type system, a package is a module, just a fancier one. The thing that gives it away is __path__, which points at a folder instead of a single file. So the rule is simple: a plain module has __file__, a package has __path__, and a package is just a module that is allowed to hold other modules.

(As before, your two paths will read differently from mine, and that is fine.) One nuance for the curious: since Python 3.3, a folder can be imported even without __init__.py (a so-called namespace package), but for your own projects always add the __init__.py. It makes your intent explicit and keeps tools and teammates happy.

Library: The Informal Umbrella Term

Here is the twist: “library” is not an official Python word at all. There is no type(requests) that returns library. It is just the casual term people use for “a chunk of code you install once and import wherever you need it.” When your teammate says “use the requests library,” they mean the thing you pip install, which on the inside is a bundle of modules and packages. Think of a library as the boxed set on the shelf: one purchase, lots of binders inside.

📄 library_example.py: libraries are bundles you install

# "requests" is what most people call a library
# What you pip install: "requests" (the distribution package)
# What you import:       "requests" (the import package)
# Here the two names match, but they do not have to!

# Example: pip install Pillow         then  import PIL
# Example: pip install beautifulsoup4 then  import bs4
# Example: pip install scikit-learn   then  import sklearn

# So the install name and the import name are sometimes different.

The Two Meanings of “Package”

This is the spot where almost everyone gets tripped up. Think of ordering a book online: the courier parcel that lands on your doorstep and the book you actually read are two different things, yet both get casually called “the package.” Python has the exact same problem. The word “package” means two completely different things, and people use both without saying which one they mean:

TermMeaningExample
Import packageA directory with __init__.py that you importimport numpy (the directory numpy/)
Distribution packageA bundle you download and install via pippip install numpy (the .tar.gz or .whl file)

For numpy the two names happen to match, so nobody notices the difference. But for Pillow the distribution package is Pillow while the import package is PIL. And with scikit-learn you run pip install scikit-learn yet write import sklearn. A quick way to keep it straight: when someone says “install the package” they mean the distribution package (the download), and when they say “import the package” they mean the import package (the folder). Same word, two jobs.

Framework: Inversion of Control

A framework is a library that flips the relationship around. With a library, you are the boss: you decide when to call its functions. With a framework, the framework is the boss: you hand it some functions, and it decides when to run them. The fancy name for this flip is “inversion of control,” but the meal-kit analogy says it best. A library is a pantry you reach into whenever you like. A framework is the meal-kit service that tells you what to cook and exactly when, while you just fill in the cooking. Say a developer named Anvi writes her first Flask app. Watch who calls whom:

📄 Library vs Framework: who calls whom?

# LIBRARY: you call it
import requests
# response = requests.get("https://api.example.com/users")
# YOU decided when to call requests.get(). You are in control.

# FRAMEWORK: it calls you
from flask import Flask
app = Flask(__name__)

@app.route("/")
def home():
    return "Hello from Anvi's app!"

# app.run()
# Flask decides WHEN to call home(): the moment a web request arrives.
# You wrote the function. Flask runs the show.

What happened here: Read the two halves side by side and the difference jumps out. In the library half, you type requests.get(...) when you want data. In the framework half, you never call home() yourself. You just register it with @app.route("/") and Flask calls it for you when a browser hits that URL. That is inversion of control in one screen. So keep this straight: requests, NumPy, and Pandas are libraries that you call. Django, Flask, FastAPI, and pytest are frameworks that call you. (The requests.get and app.run() lines are commented out on purpose, since one needs a live network and the other would start a server and never return.)

Real-World Scenarios

  • A student named Anvay asks: “Should I use a library or a framework?” → Want full control over how your app flows? Reach for libraries (something like requests plus SQLAlchemy plus Jinja2) and wire them together yourself. Want the structure handed to you? Pick a framework. Django alone gives you an ORM (Object-Relational Mapper), routing, an admin panel, and login out of the box, but in return you build things Django’s way.
  • “Is NumPy a module, package, or library?” → Honestly, all three fit depending on who is asking. Under the hood it is an import package (a folder with __init__.py) that ships as a distribution package you get from pip. In casual talk, everyone just calls it a “library.” None of those answers is wrong.
  • “Is pytest a library or a framework?” → Framework, no question. You write test functions, and pytest goes and finds them, then runs them for you. You never call pytest to kick off a test. It calls your tests. That is the give-away every time.

Decision Summary

  • Say “module” when referring to a single file you import: import math, import os
  • Say “package” when referring to a directory you import: import numpy, import email
  • Say “library” when referring to something you pip install: pip install requests
  • Say “framework” when the tool controls your code’s execution: Django, Flask, pytest
  • When in doubt, “library” is the safest catch-all word. Nobody will stop you to correct it.

Common Mistakes

❌ Mistake 1: Calling Django a “library”

# Django is a FRAMEWORK. It controls your application's flow.
# You write models, views, and URLs. Django wires them together and runs them.
# Calling it a "library" suggests you are in control. With Django, Django is.

❌ Mistake 2: Confusing distribution name with import name

# pip install Pillow       → import PIL      (not import Pillow!)
# pip install beautifulsoup4 → import bs4    (not import beautifulsoup4!)
# pip install scikit-learn  → import sklearn  (not import scikit_learn!)
# pip install python-dateutil → import dateutil
# When in doubt, check the library's docs for the real import name.

What happened here: The install name and the import name live in two separate worlds, so they do not always agree. You pay pip with one name and Python imports with another. Guessing wastes ten minutes on a ModuleNotFoundError that was never about your code. Two minutes on the project’s docs (or its PyPI page) saves you every time.

Practice Exercises

  1. Exercise 1: Import random and json, then print hasattr(mod, '__path__') for each. Confirm that the module returns False and the package returns True. That one line tells module and package apart.
  2. Exercise 2: Run pip install Pillow, then try import Pillow and watch it fail, then try import PIL and watch it work. Write a comment explaining why the install name and import name differ.
  3. Exercise 3: Make a tiny folder named mytools/ with an empty __init__.py and a file greet.py inside it. Import it with from mytools import greet. You just built your own package from scratch.

Conclusion

Here is the whole Python module vs package vs library story in four lines. A module is a single .py file. A package is a folder of modules. A library is the easygoing word for any reusable bundle you install. A framework is a library that flips control: it calls your code instead of waiting for you to call it. And remember that “package” pulls double duty in Python (the folder you import versus the bundle you pip install), so listen for which one a person means. Get these straight and the docs suddenly read a lot clearer.

Now that you understand the terminology, it is time to see the landscape. In Python libraries ecosystem tutorial, you will get a curated map of 50 libraries across web, data science, ML (Machine Learning), testing, and DevOps, the ecosystem every Python developer should know. And if you want the full roadmap from complete beginner to AI/ML, browse the Python + AI/ML tutorial series home and pick your next stop.

Frequently Asked Questions

What is the difference between a Python module and a package?

In the python module vs package vs library debate, the smallest distinction is this: a module is a single .py file (for example random.py), while a package is a folder that contains an __init__.py and one or more modules (for example json/). In Python’s type system a package IS a module: it just carries a __path__ attribute and is allowed to hold sub-modules.

What is the difference between a library and a framework in Python?

You call a library, so you control the flow. A framework calls you, so it controls the flow (this is inversion of control). requests is a library: you decide when to make HTTP calls. Flask is a framework: you define routes and Flask decides when to call your handler functions.

Is NumPy a module, package, or library?

Technically NumPy is an import package (a folder with __init__.py) that ships as a distribution package via pip. In casual talk people call it a ‘library.’ All three labels are correct depending on context, and ‘library’ is the most common everyday usage.

Why does pip install name differ from import name?

The pip distribution name and the import name live in separate namespaces. They often match (requests), but not always: pip install Pillow then import PIL, and pip install scikit-learn then import sklearn. When in doubt, check the library’s documentation for the correct import name.

Is the Python standard library a library or a package?

It is a collection of modules and packages that ship with Python. ‘Standard library’ is the official term. Some parts are plain modules (random) and some are packages (json, email). Together they form the standard library, which is what people mean by Python’s ‘batteries included.’

Interview Questions on Module vs Package vs Library vs Framework

Interviewers rarely ask for definitions. They ask what happens in situations like these.

Q: At runtime, how would you check whether an imported name is a plain module or a package?

Every import gives you a module object, so type() always says module for both. The reliable test is hasattr(mod, '__path__'): packages carry __path__ (a list of directories), while plain modules only have __file__. Also worth mentioning: built-in modules like math are compiled into the interpreter, so they have neither attribute.

Q: You run pip install requests, it succeeds, but your script still raises ModuleNotFoundError: No module named ‘requests’. What do you check first?

Check that pip and the running interpreter belong to the same environment. Compare sys.executable in your script with the Python that pip is attached to, because pip often installs into a different virtual environment or Python version than the one executing the script. Running python -m pip install requests with the exact interpreter you use removes the ambiguity, and python -m pip show requests confirms where it landed.

Q: A teammate creates a file called json.py in the project folder, and suddenly import json fails everywhere with AttributeError. What happened?

The local file is shadowing the standard library json package. The script’s own directory sits at the front of sys.path, so Python imports the teammate’s json.py instead of the real one, and calls like json.loads() blow up because that file never defined them. The fix is to rename the local file and clear any stale __pycache__ folders, and as a habit, never name your files after standard library modules.

Q: What does __init__.py actually do, beyond marking a directory as a package?

It runs once, the first time the package is imported, which makes it the place to shape the package’s public Application Programming Interface (API). Teams use it to re-export key functions from submodules (from .core import connect), define __all__, or set a version string. Since Python 3.3 the marker role is technically optional because of namespace packages, but this initialization role is why regular packages still ship one.

Q: Your team is building an internal tool and debating Flask versus wiring up plain libraries. How does the library vs framework distinction guide that call?

The distinction is inversion of control: with libraries you own the program’s flow, with a framework you hand it functions and it decides when they run. If the tool must serve HTTP requests, something has to own the request loop, so a framework like Flask earns its place. If the tool only consumes APIs or crunches files on a schedule, plain libraries keep you in control with less machinery to learn.

Q: You are publishing your own utility to PyPI. Where do you set the pip install name versus the import name, and can they differ?

The pip install name (the distribution package name) is the name field in pyproject.toml, while the import name is simply the package directory name in your source tree. They can absolutely differ, which is exactly how pip install Pillow ends up as import PIL. For a new project, keep them identical unless you have a strong reason, because mismatched names are a classic source of user confusion.

Want more? the official Python documentation documents everything this post could not fit.

Previous: Python: Standard Library, 20 Must-Know Modules

Next: Python: 50 Libraries Every Developer Should Know (The Ecosystem Map)

Series Home: Python + AI/ML Tutorial Series

RahulAuthor posts

Avatar for Rahul

Rahul is a passionate IT professional who loves to sharing his knowledge with others and inspiring them to expand their technical knowledge. Rahul's current objective is to write informative and easy-to-understand articles to help people avoid day-to-day technical issues altogether. Follow Rahul's blog to stay informed on the latest trends in IT and gain insights into how to tackle complex technical issues. Whether you're a beginner or an expert in the field, Rahul's articles are sure to leave you feeling inspired and informed.

No comment

Leave a Reply

Your email address will not be published. Required fields are marked *