Python
Set up Python with pyenv, manage virtual environments and dependencies using uv, pipenv, or poetry, and keep your projects up to date on macOS
What is Python?
Python is an interpreted, object-oriented, high-level programming language with dynamic semantics. Its high-level built-in data structures, combined with dynamic typing and dynamic binding, make it very attractive for Rapid Application Development, as well as for use as a scripting or glue language to connect existing components together.
Pythonโs simple, easy-to-learn syntax emphasizes readability and therefore reduces the cost of program maintenance. Python supports modules and packages, which encourages program modularity and code reuse.
Prerequisites
Install Pyenv
Pyenv intercepts Python commands using shim executables injected into your PATH, determines which Python version has been specified by your application, and passes your commands along to the correct Python installation.
Why use pyenv?
- Isolate Python versions
- Change the global Python version on a per-user basis
- Support for per-project Python versions
- Override the Python version with an environment variable
Install dependencies:
brew install openssl readline sqlite3 xz zlib tcl-tk
Install pyenv:
brew install pyenv
Close your terminal session and add pyenv to your ~/.zshrc or ~/.bashrc config file.
Install Python
Apple Silicon Note: Homebrew automatically installs to
/opt/homebrewon Apple Silicon Macs. In most cases, no manual migration is needed - pyenv and other tools will work correctly out of the box.
Legacy Intel Migration (rarely needed)
If you previously had an Intel Mac with packages in /usr/local/bin and are
experiencing conflicts, first verify whatโs there:
# Check what's in /usr/local/bin before making changes
ls -la /usr/local/binOnly proceed if you understand what files are present. Do not blindly move or delete system directories - this can break macOS utilities and other software. Instead, reinstall conflicting tools via Homebrew:
# Reinstall specific tools via Homebrew (preferred approach)
brew reinstall <package-name>List all available versions:
pyenv install --list
List versions by base version (e.g., v3):
pyenv install --list | grep -E " 3\.([6-9]|1[0-9])"
Install a specific version:
pyenv install <version>
Close your terminal session and add Python exports to your ~/.zshrc or ~/.bashrc
config file.
The order of the exports to the
$PATHis important. From the bottom-up of the.zshrcfile: SDKMAN! (BOTTOM), Python exports (ABOVE SDKMAN!).
Package Management
Choosing a Tool
Several tools exist for managing Python packages and virtual environments. Here is how they compare:
| Tool | Purpose | Notes |
|---|---|---|
uv | Fast Python package installer, resolver, and project manager | Modern Rust-based tool that replaces pip, virtualenv, and more |
pipenv | Dependency management and virtual environments | Replaces pip + virtualenv + requirements.txt workflow |
poetry | Dependency management, packaging, and publishing | All-in-one tool with pyproject.toml-based configuration |
For most workflows, uv is the recommended tool due to its speed and comprehensive feature set. Pipenv and Poetry are mature alternatives with large ecosystems.
Install a Package Manager
# Install uv
brew install uvVerify:
uv --version# Install pipenv
brew install pipenvVerify:
pipenv --version# Install poetry
brew install poetryVerify:
poetry --versionInstall Packages
# Add a production dependency
uv add <package-name>
# Add with a version constraint
uv add "requests>=2.28"
# Add a dev dependency
uv add --dev <package-name># Add a production dependency
pipenv install <package-name>
# Add with a version specifier
pipenv install "package_name~=1.2"
pipenv install "package_name==1.2.3"
# Add a dev dependency
pipenv install <package-name> --dev# Add a production dependency
poetry add <package-name>
# Add with a version constraint
poetry add "requests^2.28"
# Add a dev dependency
poetry add --group dev <package-name>Uninstall Packages
# Remove a specific package
uv remove <package-name># Remove a specific package
pipenv uninstall <package-name>
# Remove all packages
pipenv uninstall --all
# Remove only dev packages
pipenv uninstall --all-dev# Remove a specific package
poetry remove <package-name>
# Remove a dev dependency
poetry remove --group dev <package-name>Config File Examples
# pyproject.toml
[project]
name = "my-project"
version = "0.1.0"
requires-python = ">=3.12"
dependencies = [
"requests>=2.28",
"flask~=3.0",
]
[dependency-groups]
dev = [
"pytest>=7.0",
"ruff>=0.1",
]# Pipfile
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
requests = "*"
flask = "~=3.0"
[dev-packages]
pytest = "*"
black = "*"
[requires]
python_version = "3.12"# pyproject.toml
[tool.poetry]
name = "my-project"
version = "0.1.0"
[tool.poetry.dependencies]
python = "^3.12"
requests = "^2.28"
flask = "^3.0"
[tool.poetry.group.dev.dependencies]
pytest = "^7.0"
ruff = "^0.1"Virtual Environments
A Python virtual environment is an isolated directory tree that contains a Python installation and a set of additional packages. Virtual environments prevent dependency conflicts between projects by giving each project its own independent set of packages.
Create a Virtual Environment
uv venvThis creates a .venv directory in your project root. You can specify a Python version:
uv venv --python 3.12If using a pyproject.toml with dependencies, create the environment and install
everything in one step:
uv syncpipenv installIf no
Pipfileexists, a new virtual environment is created but no dependencies are installed. Pipenv will import from arequirements.txtif one exists.
Specify a Python version:
pipenv --python <version>poetry installTo specify a Python version:
poetry env use python3.12To create the environment in your project directory (instead of Poetryโs cache):
poetry config virtualenvs.in-project true
poetry installActivate a Virtual Environment
With uv, you typically do not need to activate the environment manually. Instead,
run commands through uv run:
uv run python script.py
uv run pytestIf you prefer manual activation:
source .venv/bin/activatepipenv shellThis will also create a virtual environment if one does not yet exist.
Run commands through Poetry without activating:
poetry run python script.py
poetry run pytestOr activate the shell:
poetry shellDeactivate a Virtual Environment
If you activated manually:
deactivateIf you used uv run, no deactivation is needed.
exitIf you used poetry shell:
exitIf you activated manually:
deactivateGenerated Files
- pyproject.toml โ Declares your projectโs dependencies and metadata
- uv.lock โ Records the exact versions of all dependencies for reproducible installs
- Pipfile โ Declares your projectโs dependencies and replaces
requirements.txt - Pipfile.lock โ Records the exact versions and hashes of all dependencies for reproducible installs
- pyproject.toml โ Declares your projectโs dependencies, metadata, and build config
- poetry.lock โ Records the exact versions of all dependencies for reproducible installs
Environment Location
uv creates the virtual environment in .venv within your project by default.
Pipenv stores virtual environments in a centralized location:
pipenv --venv
# Default: ~/.local/share/virtualenvs/To create environments inside your project instead:
export PIPENV_VENV_IN_PROJECT=1
pipenv installBy default, Poetry stores virtual environments in a centralized cache:
poetry env info --path
# Default: ~/Library/Caches/pypoetry/virtualenvs/To create environments inside your project:
poetry config virtualenvs.in-project trueUpdating Dependencies
Check What Is Outdated
uv pip list --outdatedCheck if the lockfile is up to date:
uv lock --checkpipenv update --outdatedPackages listed as โSkippedโ have version specifiers that prevent automatic updates. Packages listed as โout-of-dateโ are eligible for update.
poetry show --outdatedUpdate Individual Dependencies
Update within the existing constraint:
uv lock --upgrade-package <package_name>
uv syncChange the constraint and install latest:
uv add <package_name>@latestpipenv update <package_name>This will not overwrite the version specifier in your Pipfile. To change the
specifier and install a newer version:
pipenv install <package_name>~=<required_version>For dev dependencies, add the --dev flag:
pipenv update <package_name> --devUpdate within the existing constraint:
poetry update <package_name>Change the constraint and install latest:
poetry add <package_name>@latestFor dev dependencies:
poetry add <package_name>@latest --group devUpdate All Dependencies
uv lock --upgrade
uv syncThis re-resolves all dependencies while respecting constraints in pyproject.toml.
pipenv updateThis will not overwrite version specifiers in your Pipfile. Change specifiers
manually to allow newer major or minor versions.
poetry updatePoetry will not exceed constraints in pyproject.toml. To allow newer major versions,
update the constraint first (e.g., change ^2.0 to ^3.0).
Version Specifiers
Package versions follow Semantic Versioning (SemVer):
major.minor.micro
1 . 2 . 6
- major โ Incremented for breaking changes
- minor โ Incremented for new features (backwards compatible)
- micro (patch) โ Incremented for bug fixes
All Python tools use PEP 440 version specifiers.
The syntax is the same whether you write it in pyproject.toml or Pipfile:
Compatible Release ( ~= )
behave = "~=1.2.6"
Allows patch updates within the same minor version. ~=1.2.6 is equivalent to
>=1.2.6, <1.3.0.
| Current | Installable | Not Installable |
|---|---|---|
| 1.2.6 | 1.2.7, 1.2.9 | 1.3.0, 2.0.0 |
In this example, ~=1.2.6 would upgrade to 1.2.9 but not to 1.3.0 or 2.0.0.
Note:
~=1.2(without a micro version) allows minor updates:>=1.2, <2.0.0.
Best for: Projects that want only patch-level updates within a known minor version.
Caret ( ^ ) โ Poetry Default
# pyproject.toml (Poetry)
[tool.poetry.dependencies]
behave = "^1.2.6"
Allows changes that do not modify the leftmost non-zero digit. ^1.2.6 is equivalent to
>=1.2.6, <2.0.0. This is Poetryโs default specifier when adding packages.
Best for: Poetry projects that want compatible updates. Functionally similar to
~=for most version numbers.
Exact Match ( == )
behave = "==1.2.6"
Only ever installs the exact version specified. A typical update command will not install a newer version.
Best for: Ensuring a specific known-good version is used. Requires manual version bumps.
Less Than ( < )
behave = "<1.2.6"
Ensures no version newer than the one specified will be installed.
Less Than or Equal ( <= )
behave = "<=1.2.6"
Matches versions older than or equal to the specified version.
Greater Than ( > )
behave = ">1.2.6"
Ensures any version newer than the one specified will be installed.
Greater Than or Equal ( >= )
behave = ">=1.2.6"
The minimum installed version is the one specified, and any newer version (including new major versions) will be installed if available.
Wildcard ( * )
behave = "*"
Always installs the latest available version. Use with caution in production.
Update Strategy
- Pin production dependencies with
==or~=(or^in Poetry) for stability - Use
~=or^for libraries to allow compatible updates automatically - Check outdated packages regularly to stay current with security patches
- Test after every update to catch regressions early
- Commit your lockfile (
uv.lock,Pipfile.lock, orpoetry.lock) to version control for reproducible builds
Upgrading Python
When a new Python release is available, you may want to upgrade the Python version used by your projectโs virtual environment. There is no in-place upgrade path โ you need to recreate the environment with the new version.
Install the New Version
# List available versions
pyenv install --list | grep -E " 3\.(1[0-9])"
# Install the new version
pyenv install <new-version>
# Set it as the global version
pyenv global <new-version>
# Verify
python -V
uv can also install Python versions directly with
uv python install 3.12.
Update Your Config File
Update requires-python in pyproject.toml:
[project]
requires-python = ">=3.12"Update [requires] in your Pipfile:
[requires]
python_version = "3.12"Update the Python constraint in pyproject.toml:
[tool.poetry.dependencies]
python = "^3.12"Remove the Old Environment
rm -rf .venvpipenv --rmpoetry env remove python3.11
# Or remove all environments:
poetry env remove --allRecreate the Environment
uv venv --python 3.12
uv syncVerify:
uv run python -Vpipenv installVerify:
pipenv shell
python -Vpoetry env use python3.12
poetry installVerify:
poetry run python -VWhen to Upgrade
- Security patches (e.g., 3.11.8 to 3.11.9) โ Upgrade promptly; backwards compatible
- Minor releases (e.g., 3.11 to 3.12) โ Test your project first; some deprecations may affect your code
- End-of-life versions โ Upgrade as soon as practical to maintain security
Check the Python Release Schedule to see which versions are currently supported.
Good to Know
Check Versions
List installed versions:
ls ~/.pyenv/versions/
Check which version is being used:
pyenv versions
The version with the * next to it is the currently used version.
List the current version:
python -V
Switch Versions
pyenv global <version>
If your Python version is not changing after setting it using pyenv:
- Update pyenv:
brew update && brew upgrade pyenv- Check for a
.python-versionfile in your home directory and delete it:cd ~ && rm .python-version
Find Install Location
pyenv which python
# Output: /Users/<user>/.pyenv/versions/<version>/bin/python
which python
# Output: /Users/<user>/.pyenv/shims/python
Migrating from requirements.txt
If you have an existing project using requirements.txt:
# Import existing requirements
uv add -r requirements.txt# Auto-detect and import
pipenv install
# Or explicitly import
pipenv install -r requirements.txtGenerate requirements.txt from Pipfile:
pipenv requirements > requirements.txt
pipenv requirements --dev > requirements-dev.txt# Import existing requirements (simple files only)
poetry add $(cat requirements.txt)This approach fails on files with comments, blank lines, or directives like
-ror-e. For complexrequirements.txtfiles, pre-filter withgrep -Ev '^[[:space:]]*#|^[[:space:]]*$|^-' requirements.txt | xargs poetry addor use a migration tool like poetry-import-plugin.
Export to requirements.txt:
poetry export -f requirements.txt --output requirements.txtTroubleshooting
โpyenv: command not foundโ
-
Ensure pyenv is initialized in your shell config (
~/.zshrc):export PYENV_ROOT="$HOME/.pyenv" export PATH="$PYENV_ROOT/bin:$PATH" eval "$(pyenv init -)" -
Re-source your shell:
source ~/.zshrc
Python Version Not Changing
-
Update pyenv:
brew update && brew upgrade pyenv -
Check for local override files:
rm ~/.python-version -
Verify shims are first in PATH:
echo $PATH | tr ':' '\n' | grep pyenv
Build Errors During Installation
Missing dependencies can cause build failures:
# Install all required dependencies
brew install openssl readline sqlite3 xz zlib tcl-tk
# Set compiler flags for Apple Silicon
export LDFLAGS="-L$(brew --prefix openssl)/lib"
export CPPFLAGS="-I$(brew --prefix openssl)/include"
# Retry installation
pyenv install <version>
โpip: command not foundโ
pip should be included with Python. If missing:
# Reinstall Python with pyenv
pyenv uninstall <version>
pyenv install <version>
# Or install pip manually
python -m ensurepip --upgrade
Tool Cannot Find Python Version
# Install directly with uv
uv python install 3.12
# Or install via pyenv
pyenv install <version># List available versions
pyenv install --list | grep -E " 3\.(1[0-9])"
# Install the required version
pyenv install <version>
# Set it as global
pyenv global <version># Install the required version
pyenv install <version>
# Tell Poetry to use it
poetry env use python3.12Lock Resolution Takes Too Long
uv is designed to be fast. If resolution is slow, check for conflicting constraints:
uv lock --verbosepipenv --clear
pipenv updatepoetry cache clear pypi --all
poetry updateHash Mismatch Errors
uv lock
uv syncpipenv lock
pipenv install --ignore-pipfilepoetry lock
poetry installUninstalling Python
Uninstall via Pyenv
# List installed versions
pyenv versions
# Uninstall a specific version
pyenv uninstall --force <version>
Remove any code or aliases in your
~/.zshrcfile that reference the uninstalled Python version.
Uninstall via Homebrew
# Check which Python packages are installed
brew list | grep python
# Uninstall Python and run diagnostics
brew uninstall python
brew doctor
To uninstall a specific version, use
brew uninstall python@<version>.
Delete broken symlinks:
brew cleanup --prune-prefix
Remove remaining Python framework and binaries:
sudo rm -rf "/Applications/Python"
sudo rm -rf /Library/Frameworks/Python.framework
sudo rm -rf /usr/local/bin/python
Direct Install Removal
If Python is directly installed (without pyenv or Homebrew):
# Uninstall all pip packages
pip3 freeze | xargs pip3 uninstall -y
# Get the location
which python3
# Look in common installation directories
ls -1 /usr/local/bin/python* 2>/dev/null
Do not remove system Python from
/usr/bin. On macOS,/usr/binis protected by System Integrity Protection (SIP). Only remove third-party installations.
Clearing pip Cache
rm -rf ~/Library/Caches/pip
Resources
Fast Python package installer, resolver, and project manager
Official Pipenv documentation with tutorials and API reference
Official Poetry documentation for dependency management and packaging
Simple Python version management for installing and switching between versions
The specification that defines how package versions are structured
Python standard for version specifiers and dependency resolution
Status of all Python branches including end-of-life dates