Deep Dive into Python Virtual Environments
First article ever! Let’s get it started.
Everyone who uses Python has used virtual environments at least once. We open our project folder, run the commands below depending on our OS, install our project dependencies, and start building.
# UNIX Based systems
python3 -m venv .venv && source .venv/bin/activate
# Windows
python -m venv .venv && .venv\Scripts\activateBut what actually happens behind the scenes when we run the activation script? What do the generated files and folders do, and what do they contain?
This article answers all that, but first we need to understand what a virtual environment is.
What are Virtual Environments?
Imagine we have 2 babies in a house, baby A and baby B, and they both share several toys. One of those toys is the Omnitrix, but there’s only space for one of them in the toy pile.
- Baby A only likes the Omnitrix from the original Ben 10.
- Baby B only likes the Omnitrix from Ben 10: Omniverse.
This means that when either baby wants to play, the mother has to keep replacing the Omnitrix in the pile beforehand to avoid any of the babies crying, but this is tedious and unsustainable. So, is there any other solution?
Yes! The mother can have two separate toy piles for the two babies so one baby playing doesn’t affect the other, and they can both enjoy their own Omnitrix.
Python virtual environments work the same way. They isolate project dependencies to avoid conflicts when projects need different versions of the same package. This ensures that several projects can run on the same machine without any errors.
In the case above:
- Baby A and Baby B: 2 different projects.
- Toy pile: The global Python Installation.
- Separate toy piles: The 2 separate virtual environments.
- Omnitrix: A package like Django, FastAPI, requests and so on
- Mother: You, the developer.
Unpacking the .venv Folder
I’ll focus mainly on UNIX-based systems, as the process in Windows is a lot more complex and I haven’t explored that route yet.
If we run python3 -m venv .venv on our system (Ubuntu in my case), we can see that it generates a couple of files and folders here, namely the bin, include, lib, and lib64 folders and a pvenv.cfg file.
Here is what each component does:
bin— This folder contains the executables and scripts that your project needs. It also contains the CLI programs like fastapi, pytest, and alembic you use in your projects. For example, let’s say I activate the environment and install the FastAPI package, which also installs a terminal application.
Let’s now see how the bin folder looks:
As we can see, it added the fastapi executable to the bin folder after the installation.
You’ll notice the bin folder also contains:
- Activation scripts: Shell scripts (
activate,activate.csh,activate.fish) used to activate the environment depending on your current shell. - Package installers: Tools like
pip,uvwhich we use to install dependencies. - Python interpreter: The Python binary used to run our programs.
For ourpython executable, there’s an arrow (->) pointing to our global Python installation. This is called a symbolic link (aka symlink), which is basically like a shortcut, and what this does is that whenever we run the python3, python or python3.14, we execute the Python that is installed globally on our system.
Get Kamal Baiyewu’s stories in your inbox
Join Medium for free to get updates from this writer.
But because the binary detects the adjacent pyvenv.cfg, it dynamically re-points its sys.prefix (base directory of lib/site-packages) and package search path (sys.path) to .venv .
include— Contains C header files that some packages in your project might need.lib— This contains the folderpython{VERSION}/site-packageswhere packages are located when installed via a package manager like pip, uv, or poetry. In my case, with Python 3.14, packages are installed tolib/python3.14/site-packages/xxx.
lib64— A symlink to thelibfolder. It’s created when the 64-bit version of Python is installedpyvenv.cfg-The configuration file for our virtual environment. It contains the following:home— The base directory of the Python interpreter that the virtual environment is using.include-system-site-packages— A flag that indicates whether the globally installed packages should be used by the project instead of the ones located in theENV_NAME/lib/python{VERSION}/site-packages. By default, it is set to false.version— The version of Python being used.executable— The location of the Python executable used.command— The command used to create the environment.
venv module.The built-in site module uses the settings above to set up our virtual environment.
Activating the Virtual Environment
Now that we’ve broken down the contents of the .venv folder, let’s take a look at the magic that happens when we run the command source .venv/bin/activate in our shell. In order:
- The
activatescript first createsdeactivate, which undoes the actions listed below. - It sets the
VIRTUAL_ENVenvironment variable to point to the absolute path of the.venvfolder. - Adds
VIRTUAL_ENV/binto the beginning of the terminal’sPATHenvironment variable. By doing this, whenever we runpythonwithin our environment, it uses the Python located in the.venvfolder.
4. Our shell prompt is also tweaked to show that we are currently in a virtual environment.
I’m committing to writing one technical breakdown per week to solidify what I learn and help others along the way.
Thanks for reading, and have a wonderful day.




