Documentation Index
Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
Use this file to discover all available pages before exploring further.
Modules & Packages
Real-world Python projects aren’t single files. They are split across multiple files (modules) and directories (packages). Understanding how to organize code and manage dependencies is crucial. Think of modules as individual LEGO bricks — each one does something specific. Packages are the organized trays that hold related bricks together. Andpip is the store where you buy bricks other people made. Without this organization system, every Python project would be a single 10,000-line file, and collaboration would be impossible.
1. Modules
A Module is simply a Python file (.py). Any Python file can be imported by another.
Importing
You can import the whole module or specific parts.__name__ == "__main__"
This is a common idiom. It checks if the file is being run directly (like python main.py) or imported as a module.
2. Packages
A Package is a directory containing Python modules. It requires a__init__.py file (Python 3.3+ made this optional via “namespace packages,” but including it is still strongly recommended — it is the explicit signal that “this directory is a Python package”).
What goes in __init__.py?
The __init__.py file runs when the package is imported. Use it to define the package’s public API:
3. The Standard Library
Python is famous for being “Batteries Included”. It has a massive standard library built-in.pathlib (Modern File Paths)
Stop using os.path.join. Use pathlib. It treats file paths as objects rather than strings, which makes path manipulation safer and more readable. It works on Windows, Mac, and Linux seamlessly — no more worrying about / vs \.
json (Data Serialization)
JSON is the language of the web. Python handles it natively.
datetime (Dates & Times)
4. Virtual Environments (venv)
The Golden Rule of Python: Never install packages globally. Always use a virtual environment.
A virtual environment creates an isolated folder for your project’s dependencies. Think of it as giving each project its own private copy of Python and its libraries. This prevents “Dependency Hell” where Project A needs requests==2.28 and Project B needs requests==2.31, and installing one breaks the other.
Setup
(.venv) C:\Project>). Now, when you run pip install, packages go into this folder, not your system Python.
Modern Alternative: uv
The Python packaging ecosystem is evolving. uv (from Astral, the makers of ruff) is a fast, Rust-based replacement for pip, venv, and pip-tools combined. It creates virtual environments, resolves dependencies, and installs packages 10-100x faster than pip.
5. Package Management (pip)
pip is the package installer for Python. It fetches packages from PyPI (Python Package Index).
Example: Using requests
requests is the most popular Python library. It makes HTTP requests simple.
Summary
- Modules:
.pyfiles. - Packages: Folders with
__init__.py. - Standard Library: Learn
pathlib,json,datetime. - venv: Always isolate your projects.
- pip: The tool to install external libraries.
Interview Deep-Dive
Explain Python's import system in detail. What happens when you write `import foo`? What is the difference between absolute and relative imports?
Explain Python's import system in detail. What happens when you write `import foo`? What is the difference between absolute and relative imports?
- When you write
import foo, Python executes a multi-step process. First, it checkssys.modules— a cache of all previously imported modules. Iffoois already there, it returns the cached module object immediately. No file is read, no code is executed. This is why importing the same module in 50 files does not run the module code 50 times. - If the module is not cached, Python searches for it using
sys.path— an ordered list of directories.sys.pathincludes: the directory of the script being run, directories set in thePYTHONPATHenvironment variable, the standard library paths, and thesite-packagesdirectory (where pip installs packages). Python searches these in order, and the first match wins. - Once found, Python compiles the module to bytecode (if a cached
.pycis not up to date), executes the module’s top-level code (all statements at the module level run during import), and stores the resulting module object insys.modules. This is why putting side effects (print statements, database connections, API calls) at the module level is dangerous — they execute on import, which might be at test collection time, CI startup, or other unexpected moments. - Absolute imports (
from package.sub import module) use the full path from the project root. Relative imports (from .sub import moduleorfrom ..sibling import func) use dots to navigate relative to the current package. Relative imports only work inside packages (not in top-level scripts). One dot means “current package,” two dots mean “parent package.” - A common production issue: circular imports. Module A imports module B, and module B imports module A. This does not always fail — Python handles it by returning a partially-initialized module from
sys.modules. But if B tries to access a name from A that has not been defined yet (because A’s top-level code has not finished executing), you get anImportErrororAttributeError. The fix is to restructure the code (extract shared code to a third module), use lazy imports (import inside the function that needs it), or useTYPE_CHECKINGblocks for type-hint-only imports.
if __name__ == "__main__" actually doing, and what is the __name__ variable?- Every Python module has a
__name__attribute. When a file is run directly (python script.py),__name__is set to the string"__main__". When the same file is imported as a module (import script),__name__is set to the module’s qualified name (e.g.,"script"or"package.script"). - The
if __name__ == "__main__"guard prevents code from running when the module is imported. Without it, any top-level code (test runs, demo output, server startup) would execute on import, which breaks test discovery, IDE introspection, and module reuse. - This pattern also makes your module both a library and a script. The functions and classes are importable by other code, and the
__main__block provides a command-line entry point. This is a foundational Python pattern that every production module should use for any executable behavior.
Compare `pip`, `poetry`, `pipenv`, and `uv` for dependency management. What are the trade-offs, and what would you use for a new production project today?
Compare `pip`, `poetry`, `pipenv`, and `uv` for dependency management. What are the trade-offs, and what would you use for a new production project today?
pipis the standard, built-in package installer. It installs packages from PyPI and supportsrequirements.txtfor reproducibility. Its weakness is that it does not do dependency resolution well — if package A needsrequests>=2.0and package B needsrequests<2.25, pip might install a conflicting version. It also does not distinguish between direct dependencies and transitive dependencies, makingrequirements.txta flat dump that is hard to audit.poetryprovides a full project management experience: dependency resolution, lockfiles (poetry.lock), virtual environment management, and package building/publishing. It usespyproject.tomlfor configuration (PEP 621 compliant). Its dependency resolver is deterministic — it computes a locked set of versions that satisfy all constraints and records them. The downside is that Poetry is opinionated (it manages your virtualenv for you, which can conflict with Docker workflows) and its resolution can be slow for complex dependency trees.pipenvwas an earlier attempt at combining pip and virtualenv with aPipfile/Pipfile.lockworkflow. It fell out of favor due to slow resolution, inconsistent maintenance, and confusing behavior around lock file generation. Most teams have migrated to Poetry or uv.uvis the newest contender, built in Rust by the creators ofruff. It is a drop-in replacement for pip and pip-tools that is 10-100x faster. It handles dependency resolution, virtual environment creation (uv venv), and lockfile generation (uv lock). It supportspyproject.tomland is rapidly becoming the default recommendation for new projects due to its speed and compatibility.- For a new production project today, I would use
uvfor speed and simplicity, withpyproject.tomlfor project metadata. For teams already on Poetry with established workflows, there is no urgency to migrate. The key principle is: always have a lockfile (deterministic builds), always separate direct dependencies from transitive ones, and always use a virtual environment.
requirements.txt and a lockfile, and why does it matter for production deployments?requirements.txtwith pinned versions (requests==2.31.0) specifies direct dependencies but not the exact versions of their transitive dependencies. Ifrequestsdepends onurllib3, and you do not pinurllib3, you might get different versions on different machines or at different times, leading to “works on my machine” problems.- A lockfile (Poetry’s
poetry.lock, uv’suv.lock) records the exact version of every package in the dependency tree — direct and transitive — along with content hashes. This guarantees thatuv syncon your laptop, in CI, and on the production server installs byte-for-byte identical packages. Without a lockfile, you are onepip installaway from a surprise breaking change in a transitive dependency. - The practical workflow:
pyproject.tomldeclares what you need (loose constraints), the lockfile records what you got (exact versions). Developers update constraints inpyproject.toml, regenerate the lockfile, test, and commit both. Production deployments install from the lockfile only.
What is `__init__.py` and what role does it play in Python packages? What changed with namespace packages in Python 3.3+?
What is `__init__.py` and what role does it play in Python packages? What changed with namespace packages in Python 3.3+?
- Historically,
__init__.pywas required to mark a directory as a Python package. Without it, Python would not recognize the directory as importable. The file is executed when the package is first imported, and the resulting module object becomes the package itself. Soimport mypackagerunsmypackage/__init__.pyand gives you access to whatever names are defined there. __init__.pyserves several practical purposes: it controls the public API of the package (by importing select names from submodules), it can run package initialization code (setting up logging, loading configuration), and it defines__all__(which controls whatfrom package import *exports). A common pattern is to import the most-used classes into__init__.pyso users can writefrom mypackage import MyClassinstead offrom mypackage.submodule import MyClass.- Python 3.3 introduced namespace packages (PEP 420), which allow packages without
__init__.py. The motivation was to allow a single logical package to be split across multiple directories or distributions. For example, thegooglenamespace package letsgoogle-cloud-storageandgoogle-authboth install into thegoogle/directory without conflicting__init__.pyfiles. - In practice, most application code should still use
__init__.py. Namespace packages are primarily for large library ecosystems (Google Cloud, Azure SDK) where multiple independent teams publish sub-packages under a shared namespace. Omitting__init__.pyin application code causes confusion and breaks some tooling (test discovery, IDE imports, some linters). - A production best practice: keep
__init__.pyfiles minimal. Heavy initialization code (database connections, config parsing) should not live there because it runs on import. If importing a package triggers a database connection, you cannot import it in tests, scripts, or type-checking contexts without that side effect. Lazy initialization patterns or explicitinit()functions are better.
__all__ variable and how does it affect imports?__all__is a list of strings that defines the public API of a module. It controls two things: whatfrom module import *exports, and what tools likemypyand IDEs consider “public.”- Without
__all__,from module import *imports every name that does not start with an underscore. With__all__ = ["ClassA", "function_b"], only those specific names are imported. This is important for packages with many internal helper functions that should not leak into the user’s namespace. __all__does not prevent direct access —from module import _private_funcstill works. It is a convention, not an access control mechanism. But it is respected by documentation generators (Sphinx), linters, and IDE autocompletion, making it a valuable tool for API design.
You join a team and find a Python project with no virtual environment, globally installed packages, and no requirements file. How do you fix this without breaking the existing deployment?
You join a team and find a Python project with no virtual environment, globally installed packages, and no requirements file. How do you fix this without breaking the existing deployment?
- Step one: understand the current state. Run
pip liston the production machine (or whoever has the “working” environment) to get the exact set of installed packages. Save this withpip freeze > requirements-snapshot.txt. This is your baseline — it captures the exact versions that are known to work, including transitive dependencies. - Step two: create a virtual environment on a development machine.
python -m venv venv, activate it, and install from the snapshot:pip install -r requirements-snapshot.txt. Run the full test suite and verify the application works identically. If there are no tests, run the application manually and verify key flows. - Step three: separate direct dependencies from transitive ones. Go through the snapshot and identify which packages the code actually imports (grep the codebase for
importstatements). Create apyproject.tomlorrequirements.inwith only the direct dependencies and their version constraints. Usepip-compile(from pip-tools) oruv pip compileto regenerate a lockfile from the direct dependencies. Verify the resolved versions match the working snapshot. - Step four: set up the virtual environment in CI/CD. The build pipeline should create a fresh venv and install from the lockfile. If the deployment is containerized (Docker), the Dockerfile should create a venv inside the container — even in Docker, a venv keeps system Python clean and makes it clear what is application code versus OS dependencies.
- Step five: document the process and add it to the contribution guide. The most common reason projects end up in this state is that the setup process was never documented, so each developer improvised. A
Makefileorjustfilewith targets likemake setup,make test,make lockprevents regression. - The key principle: do not change anything in production until the new setup is proven equivalent. Freeze the current state first, reproduce it in isolation, and only then start improving.
pip freeze output and a proper lockfile?pip freezeoutputs every installed package at its current version, but it does not record which packages are direct dependencies versus transitive, it does not include content hashes (so you cannot verify package integrity), and it does not record the Python version or platform constraints. Two different runs ofpip install -r requirements.txtcan resolve transitive dependencies differently if the constraint ranges overlap.- A proper lockfile (from pip-tools, Poetry, or uv) records the full dependency graph with hashes, distinguishes direct from transitive dependencies, and is deterministic — installing from it produces the exact same environment every time. The lockfile is what you deploy; the direct dependency list is what you edit.