The “System Python” is the interpreter that comes pre-installed with your operating system (typically found at /usr/bin/python3 on macOS and Linux). This interpreter exists primarily to run various system utilities and tools, and should not be used for your personal development work.
Using system Python directly for your projects introduces three main problems:
1. Risk of breaking system utilities
Some system utilities depend on specific versions of Python packages. Installing, upgrading or removing packages in the system environment can make those utilities stop working. To mitigate this, newer OS releases enforce PEP 668 EXTERNALLY-MANAGED marker, which tells tools such as pip to refuse installations outside virtual environments by default. This helps protect packages managed by the operating system or another package manager.
2. Limited control over the Python version
The system interpreter’s version is chosen by its distributor. Your projects may need something different: one might use features introduced in Python 3.12, while another depends on libraries that only support Python 3.10. A single interpreter cannot satisfy both requirements. Installing Python versions separately gives you control over which version each project uses.
3. Shared dependencies across projects
When projects use the same Python environment, they share its installed packages. If Project A requires version 1.0 of a library and Project B requires version 2.0, installing one version can break the other project. A separate virtual environment for each project helps to keeps its dependencies isolated, so that changes to one project’s packages does not affect another.
How to Check Your System Python?
To see if you have accidentally installed third-party packages into your system Python for macOS/Linux, run this command:
/usr/bin/python3 -m pip listThe following are some of the expected packages you might find:
NOTE
Your local list might look slightly different depending on your specific operating system version and the developer tools you have installed.
Expected Packages For macOS
| Package | Purpose |
|---|---|
pip | The standard package installer for Python. It fetches and installs software packages from the Python Package Index (PyPI). |
setuptools | A core library designed to faciliate packaging Python projects. It gives Python the ability to build, install, and manage the metadata of software packages. |
wheel | A build-package format for Python. It allows packages to be installed much faster than compiling them from source code. |
six | A compatibility library designed to allow developers to write code that runs on both the deprecated Python 2 and the modern Python 3 without modification. |
future | Similar to six, this is another compatibility layer used to bridge the gap between Python 2 and Python 3 syntax in legacy scripts. |
macholib | A highly specific macOS utility used to read, analyze, and edit Mach-O headers. Mach-O is the native executable file format used by macOS and iOS. |
altgraph | A graph (network) representation library. Apple uses it behind the scenes, often in conjunction with macholib, to map out the dependency trees of macOS executable files. |
Expected Packages For Linux (Debian & Ubuntu-based)
| Package | Purpose |
|---|---|
apt-pkg | Python bindings for the Advanced Package Tool (APT). This allows Python-based OS scripts to interact directly with the system’s software updater to install or remove Debian packages (.deb). |
debconf | A module that interacts with the Debian configuration management system. When you install a system package and a blue screen pops up asking you a configuration question, debconf is often handling that prompt. |
ufw | Uncomplicated Firewall. The actual command-line tool used to manage the system’s firewall on Ubuntu is written in Python, so its core modules live here. |
apparmor | Python bindings for AppArmor, a kernel security module that restricts individual programs’ capabilities. |
dbus-python | Bindings for D-Bus, a system that allows different software applications to communicate with each other. It is heavily used by Linux desktop environments to pass messages (e.g., telling the OS a USB drive was plugged in). |
PyGObject | A wrapper that allows Python to interface with GObject-based libraries, most notably GTK. This is heavily used by GNOME and other desktop environments to draw graphical user interfaces. |
distro | A lightweight package used to reliably extract detailed information about the specific Linux distribution and version currently running. |
The following are common third-party libraries that you should NOT see on your list: requests, numpy, pandas, matplotlib, Flask, Django, FastAPI. Seeing these means someone (or an automated script) accidentally installed packages globally.
What to Use Instead?
To manage your projects independently of system Python, you need two things:
- Python version management, so each project can use the version it needs.
- Dependency isolation, so installing packages for one project does not affect another.
Historically a common approach is to combine pyenv for version management with venv for isolation. As of the time of writing this, we now have uv which brings both capabilities into one tool, alongside dependency management.
1. Using uv on a project
Whether you are starting from scratch or working on an existing codebase, open a terminal in your project directory and run:
uv inituv will scan your system and pick the best Python version for the job, writing it down in a local .python-version file.
2. Adding packages
To install dependencies (like requests or pandas), use the add command:
uv add requests3. Running your code
You can completely skip activating virtual environments using the uv run command:
uv run main.py