Package pinning means requiring a dependency to use a specified version, rather than allowing it to move to any newer acceptable release. In a quant project, pins help preserve the numerical software used for a valuation or backtest, so a later installation does not silently select different package versions.
Pinning is one part of dependency management. It constrains which versions may be installed; it does not establish that the model, data or resulting numbers are correct.
Read the version requirement carefully
In Python packaging, a conventional exact version pin uses
== without a wildcard. These requirements express different policies:| Requirement | What it permits |
|---|---|
numpy==2.5.1 | The specified public version |
numpy==2.5.* | Versions matching the 2.5 prefix; this is not an exact pin |
numpy~=2.5.1 | Compatible-release range: at least 2.5.1, within the 2.5 release series |
numpy>=2.5.1 | A lower bound, with no upper bound |
These are version-selection rules, not evidence that every permitted release exists or works with your code. Python version comparison also normalizes release segments; a public-version requirement can accept a matching local build label. For example,
==2.5.1 also matches 2.5.1+desk.1. A version pin therefore does not identify a unique downloadable file. See PyPA's version-matching specification.Exact pins are especially useful for replaying a particular application or research run. A reusable library usually needs tested compatibility ranges instead: forcing every downstream user onto the author's exact versions makes upgrades and security fixes harder.
Observe first, then record
The following code reads the selected interpreter and installed distribution metadata. It was executed with Python 3.14.4, NumPy 2.5.1 and SciPy 1.18.0. These are observed versions, not a claim that they are the latest releases or the right choices for every project. Your output may differ.
import platform
from importlib.metadata import version
print(f"Python {platform.python_version()}")
for package in ("numpy", "scipy"):
print(f"{package}=={version(package)}")
# Python 3.14.4
# numpy==2.5.1
# scipy==1.18.0The final two lines could be package requirements in a pip requirements file. The Python line is a separate interpreter record; it is not a pip dependency. No installation is performed by this example.
Observed versions distinguish the interpreter record from package pins. Two package lines do not capture the whole calculation.
A pip requirements file is an installation input, not automatically a complete environment specification. It may contain unpinned names, ranges, exact pins, direct references or pip options. Its filename alone says nothing about how tightly the environment is controlled. The requirements-file reference defines that format.
An inventory is useful; check what it represents
pip freeze reports installed packages in requirements format. It can capture installed transitive dependencies as well as the packages you chose directly. However, it describes the environment you already have: that may include unrelated exploratory packages. It does not calculate a lockfile or a dependency-solver result.Defaults also matter. On Python 3.12 and later,
pip freeze omits pip itself; --all includes the otherwise omitted packaging tools. Neither output records the interpreter version, operating system or market-data snapshot. These details follow the checked pip 26.2.2 documentation; the inventory command was not run in the example above.Preserve the rest of the run
Direct pins leave unlisted transitive dependencies free to vary within their requirements. Record the complete resolved dependency set for the intended environment. To identify approved distribution artifacts, add verified hashes: pip's hash-checking mode requires all requirements and dependencies to be explicitly pinned and hashed. Retaining the artifacts also helps when their original source becomes unavailable. See secure installs and repeatable installs.
A compiled wheel can depend on a particular Python ABI, operating system and architecture (see platform compatibility tags); native numerical libraries and execution settings can affect results too. Matching package versions across machines does not by itself promise identical floating-point output.
For a reproducible environment, retain the interpreter and platform details alongside code revision, configuration, original data identifiers and any random seeds. Use a virtual environment to isolate packages, and test the replay against an appropriate numerical tolerance. When upgrading, update the recorded environment deliberately and rerun the relevant tests: a pin preserves a choice, including an obsolete or faulty one.
For the practical workflow, Virtual Environments covers interpreter selection and isolation; Reproducibility builds a run record. Both have public previews; their full lessons require membership.
Sources
- pip 26.2.2 — Repeatable Installs: version pins, artifact hashes and wheelhouse portability.
- PyPA — Version specifiers: exact matching, prefixes, compatible releases and local labels.
- pip 26.2.2 — pip freeze: installed-package inventory and default exclusions.
- pip 26.2.2 — Secure installs: requirements of hash-checking mode.
- pip 26.2.2 — Requirements File Format: requirements files as pip installation inputs.
- PyPA — Platform compatibility tags: interpreter, ABI and platform compatibility of distributions.