DEV Community

Cover image for Python virtual environments: Inside .venv (Anatomy)
Tlaloc-Es
Tlaloc-Es

Posted on

Python virtual environments: Inside .venv (Anatomy)

Python virtual environments (python -m venv) are not magic: they are a specific directory layout plus pyvenv.cfg that controls where Python imports and where pip installs.
When installs land in the “wrong” place or imports behave inconsistently, the evidence is inside .venv/.

This guide maps each folder and file (Scripts/bin, site-packages, .dist-info, pyvenv.cfg) to the behavior it drives, so you can debug environment issues by inspection.

Virtual environment structure on Linux/macOS

A typical environment looks like this:

.venv/
├── pyvenv.cfg
├── bin/
├── include/
└── lib/
    └── python3.12/
        └── site-packages/
Enter fullscreen mode Exit fullscreen mode

What each part does

  • pyvenv.cfg: declares the environment and base Python.
  • bin/: executables and scripts like python and pip.
  • include/: headers for compiling native extensions.
  • site-packages/: where code installed by pip lives.

What changes on Windows?

The logic is the same, but paths differ:

.venv/
├── pyvenv.cfg
├── Scripts/
└── Lib/
    └── site-packages/
Enter fullscreen mode Exit fullscreen mode

Key difference: Scripts/ is the Windows equivalent of bin/.

pyvenv.cfg: the file in control

Real example:

home = C:\Python312
include-system-site-packages = false
version = 3.12.4
Enter fullscreen mode Exit fullscreen mode

What matters most:

  • home points to the base interpreter
  • include-system-site-packages controls isolation

If set to true, your environment can see global packages. Useful in edge cases, risky by default.

bin/Scripts: why pip installs in the right place

Inside the environment, pip is not magic. It is a script that invokes the environment Python.

That is why this pattern is so safe:

python -m pip install requests
Enter fullscreen mode Exit fullscreen mode

Instead of trusting whichever pip is in PATH, you force interpreter-installer consistency.

site-packages and .dist-info: your best debugging source

When you install a library, you usually get two things:

  • The package itself (importable source)
  • Its .dist-info directory (metadata)

Conceptual example:

site-packages/
├── requests/
└── requests-2.32.0.dist-info/
Enter fullscreen mode Exit fullscreen mode

Inside .dist-info you will find files like:

  • METADATA: version, dependencies, authors
  • RECORD: list of installed files
  • INSTALLER: who installed it (for example, pip)

When uninstall fails, RECORD often explains why.

Inspect installed packages from Python

from importlib.metadata import version, distributions

print("requests:", version("requests"))

for dist in distributions():
    print(dist.metadata["Name"], dist.version)
Enter fullscreen mode Exit fullscreen mode

This technique is gold for quick audits of Python environments.

Common problem: moving a .venv between machines

A virtual environment is not portable across different systems.

Reasons:

  • absolute paths in scripts
  • platform-specific compiled binaries
  • architecture and ABI differences

Practical rule: never copy .venv between machines. Recreate it from pyproject.toml/lock file or requirements.

Mini anatomy checklist for troubleshooting

When an environment “breaks,” check this first:

  1. Does pyvenv.cfg exist?
  2. Do python and pip point to the same environment?
  3. Does site-packages contain the expected package?
  4. Are there version conflicts in .dist-info?

This checklist solves a large percentage of import and install issues.

Common mistakes

  • Deleting or editing pyvenv.cfg manually, then wondering why the environment no longer behaves like a venv.
  • Running pip/python from different locations (Scripts/bin vs system PATH), so installs go to one interpreter and your code runs on another.
  • Trying to “fix” installs by deleting random folders in site-packages/ (especially .dist-info/), which can leave the environment inconsistent.
  • Moving or copying .venv/ between machines/OSes and expecting it to remain valid.

Pro tip: visibility into orphaned environments

Across many repos, old .venv/ directories pile up and you stop knowing what is safe to delete. KillPy can scan for environments and list likely-orphaned ones so you can clean up after verifying they are unused.

Conclusion

Understanding the anatomy of Python virtual environments changes how you debug:

  • you know where to look
  • you understand what pip is doing
  • you detect inconsistencies before production

The natural next step is pyproject.toml: how to declare modern dependencies and build reproducible projects.


What is the weirdest venv/virtualenv symptom you have had to debug (wrong pip, broken imports, uninstall failures)? Share it in the comments with your OS, and I will point you to the exact file(s) to inspect.


Previous

If you have not read the fundamentals yet:

https://dev.to/tlaloces/python-venv-explained-stop-breaking-dependencies-4k9k

Next

In the next step, we move from inspecting environments to defining them:

→ pyproject.toml: Modern Python Dependency Management (coming next)

Top comments (0)