- Posted on
- • Artificial Intelligence
Artificial Intelligence Python Best Practices
- Author
-
-
- User
- linuxbash
- Posts by this author
- Posts by this author
-
AI in Python on Linux: Bash-first Best Practices That Prevent “Works on My Machine” Moments
You can train a model that beats a benchmark—but if your coworkers can’t reproduce your results or deploy your code, the win won’t ship. This post shows you how to build AI/ML Python projects on Linux the way seasoned engineers do: reproducible environments, pinned dependencies, automated quality checks, and simple Bash-first workflows.
You’ll leave with copy-pasteable commands that work on Debian/Ubuntu, Fedora/RHEL, and openSUSE, and a minimal project template you can adapt today.
Why this matters
Reproducibility beats heroics: locked dependencies and scripted setups avoid “it runs only on my laptop.”
Speed with confidence: linters, formatters, and tests catch issues before you burn time on long training jobs.
Collaboration at scale: a predictable layout and one-command setup lowers the barrier for teammates and CI.
Prerequisites: install system packages
Install Python, git, build tools (for packages with native extensions), and pipx (to manage CLI tools cleanly).
- Debian/Ubuntu (apt):
sudo apt update
sudo apt install -y python3 python3-pip python3-venv pipx git build-essential pkg-config python3-dev
pipx ensurepath
- Fedora/RHEL (dnf):
sudo dnf groupinstall -y "Development Tools"
sudo dnf install -y python3 python3-pip pipx git python3-devel pkgconf-pkg-config
pipx ensurepath
- openSUSE (zypper):
sudo zypper refresh
sudo zypper install -y python3 python3-pip python3-venv pipx git gcc gcc-c++ make pkg-config python3-devel
pipx ensurepath
Open a new shell if pipx was just installed so ~/.local/bin is on PATH.
1) Isolate and script your environment
Use venv for isolation and a tiny Bash script so anyone can set up and run the project the same way.
- Create a project layout:
mkdir -p ai-project/{src/ai_project,tests}
cd ai-project
git init
- Minimal run script (create venv if missing, install locked deps, run):
cat > run.sh << 'EOF'
#!/usr/bin/env bash
set -euo pipefail
VENV="${VENV:-.venv}"
PY="${PYTHON:-python3}"
if [ ! -d "$VENV" ]; then
"$PY" -m venv "$VENV"
"$VENV/bin/python" -m pip install --upgrade pip wheel
fi
if [ -f requirements.txt ]; then
"$VENV/bin/pip" install -r requirements.txt
fi
exec "$VENV/bin/python" -m ai_project.main "$@"
EOF
chmod +x run.sh
- A tiny main program:
cat > src/ai_project/main.py << 'EOF'
def main():
print("Hello, reproducible AI world!")
if __name__ == "__main__":
main()
EOF
- Make it importable:
echo "__version__ = '0.1.0'" > src/ai_project/__init__.py
Run it:
./run.sh
Why this helps:
venv avoids polluting system Python.
One script means fewer “what did you do to set this up?” Slack messages.
2) Pin and lock dependencies
Use pip-tools to manage exact versions for deterministic installs.
- Install pip-tools once with pipx:
pipx install pip-tools
- Declare top-level deps (unpinned) in requirements.in:
cat > requirements.in << 'EOF'
numpy
pandas
scikit-learn
EOF
- Compile a fully pinned lockfile:
pip-compile --generate-hashes -o requirements.txt requirements.in
- Sync your venv to match the lockfile exactly:
./.venv/bin/python -m pip install --upgrade pip
./.venv/bin/pip install --require-hashes -r requirements.txt || ./run.sh
(Hint: if .venv doesn’t exist yet, ./run.sh will create it, then you can rerun the pip install command. Or just rely on run.sh to install from requirements.txt automatically.)
- For development tools, keep them separate:
cat > requirements-dev.in << 'EOF'
pytest
pytest-cov
mypy
EOF
pip-compile --generate-hashes -o requirements-dev.txt requirements-dev.in
- Install dev-only deps when you need them:
./.venv/bin/pip install --require-hashes -r requirements-dev.txt
Why this helps:
requirements.in stays human-curated, requirements.txt is machine-pinned.
Hashes protect against supply-chain tampering.
Alternative: Prefer Poetry? Install via pipx and use pyproject.toml:
pipx install poetry
poetry init -n
poetry add numpy pandas scikit-learn
poetry lock
poetry run python -m ai_project.main
3) Automate code quality (ruff, black, mypy) with pre-commit
- Install CLI tools with pipx (keeps them isolated from your project env):
pipx install ruff
pipx install black
pipx install mypy
pipx install pre-commit
- Add a pre-commit config:
cat > .pre-commit-config.yaml << 'EOF'
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.5.5
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
- repo: https://github.com/psf/black
rev: 24.4.2
hooks:
- id: black
- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.10.0
hooks:
- id: mypy
additional_dependencies: [types-requests]
EOF
- Activate hooks:
pre-commit install
pre-commit run --all-files
Why this helps:
Fast feedback on style and correctness before you push.
The same checks run locally and in CI.
4) Test the parts that matter
- Add a simple test:
cat > tests/test_main.py << 'EOF'
from ai_project.main import main
def test_main_prints(capsys):
main()
out, _ = capsys.readouterr()
assert "Hello, reproducible AI world!" in out
EOF
- Run tests:
./.venv/bin/pytest -q
- Add coverage in CI runs later:
./.venv/bin/pytest --cov=ai_project --cov-report=term-missing
Why this helps:
- Quick, focused tests catch refactors that break training or inference.
5) Reproducible experiments: config + seeding + provenance logs
- Centralize randomness control:
cat > src/ai_project/repro.py << 'EOF'
import os, random
import numpy as np
def seed_everything(seed: int = 42):
os.environ["PYTHONHASHSEED"] = str(seed)
random.seed(seed)
np.random.seed(seed)
try:
import torch
torch.manual_seed(seed)
torch.cuda.manual_seed_all(seed)
torch.use_deterministic_algorithms(True) # PyTorch 2.0+
except Exception:
pass
EOF
- Minimal config pattern with argparse + YAML:
cat > src/ai_project/config.py << 'EOF'
import argparse, yaml
from pathlib import Path
def load_config():
p = argparse.ArgumentParser()
p.add_argument("--config", type=Path, default=Path("config.yaml"))
args = p.parse_args()
with args.config.open() as f:
return yaml.safe_load(f)
EOF
- Example config:
cat > config.yaml << 'EOF'
seed: 2024
model:
type: "RandomForestClassifier"
n_estimators: 200
data:
path: "data/train.csv"
EOF
- Wire it into main.py:
cat > src/ai_project/main.py << 'EOF'
from .repro import seed_everything
from .config import load_config
def main():
cfg = load_config()
seed_everything(cfg.get("seed", 42))
print(f"Config: {cfg}")
print("Hello, reproducible AI world!")
if __name__ == "__main__":
main()
EOF
- Log provenance (git commit + package versions):
cat > src/ai_project/provenance.py << 'EOF'
import subprocess, sys
def git_commit():
try:
return subprocess.check_output(["git", "rev-parse", "HEAD"], text=True).strip()
except Exception:
return "unknown"
def pip_freeze():
try:
return subprocess.check_output([sys.executable, "-m", "pip", "freeze"], text=True)
except Exception:
return ""
EOF
Use it before/after training to write a small run.json or append to a log file. Even this lightweight logging makes debugging and papers/notes reproducible.
Real-world example: upgrading numpy safely
- You want a newer numpy. Instead of pip install -U numpy (which can break dependencies):
- Edit requirements.in and bump the constraint, e.g., numpy>=2.0,<2.1
- Recompile and test:
pip-compile --upgrade --generate-hashes -o requirements.txt requirements.in
./.venv/bin/pip sync -r requirements.txt
pre-commit run --all-files
./.venv/bin/pytest -q
- Commit the updated requirements.txt with a clear message.
This keeps your team and CI on the exact same versions.
Optional: Python versions with pyenv
If you need multiple Python versions, install pyenv. It’s source-based, so you’ll need build deps (already installed above). Then:
curl https://pyenv.run | bash
# Add init lines to your shell (per installer output), then:
exec "$SHELL"
pyenv install 3.11.9
pyenv local 3.11.9
python -m venv .venv && . .venv/bin/activate
python -m pip install --upgrade pip wheel
This keeps your runtime aligned with production or specific libraries.
Conclusion and next step (CTA)
AI success isn’t just about models—it’s about shipping reliable, reproducible code. You now have:
A Bash-first workflow that bootstraps itself
Locked dependencies with pip-tools
Automated quality gates with ruff/black/mypy/pre-commit
Seeds, configs, and provenance for reproducible experiments
Next step: turn one of your existing notebooks into a script in src/ and wire it to run.sh. Commit a requirements.in, compile, and run tests. If you get stuck, start a fresh repo with the snippets above and migrate code piece by piece.
Your future self—and your teammates—will thank you.