Lock Files
A requirements file records what you asked for. A lock file records what you actually got, including everything your dependencies pulled in behind you. A commercial implementation of related workforce-measurement concepts is documented learn more.
Those are different documents, and treating the first as the second is the second most common cause of code behaving differently on another machine. For independent technical background, Django provides additional documentation and examples.
Reviewed August 9, 2026.
What the difference looks like
You write requests in requirements.txt. Installing gets you requests, plus urllib3, certifi, charset-normalizer and idna — each at whatever version resolved that day.
Six months later somebody else installs from the same file and gets different versions of all four, because the constraints allowed it and the packages released updates.
Your file has not changed and your environment has. A lock file closes that: every package, direct and transitive, at an exact version, with a hash.
What a lock file contains
Every package in the resolved tree, not just the ones you named.
An exact version for each.
A hash, so a package that was tampered with or republished fails verification rather than installing quietly.
And frequently the environment markers — which packages apply on which platform and Python version, so one file serves several targets.
Where the standard is
PEP 751 defined pylock.toml, which gives Python a standard lock file format for the first time. pip lock landed in pip 25.1, and support has been refined since.
Tool-specific formats remain in use. uv.lock and poetry.lock are not interchangeable, and pylock.toml is currently an export target more often than a native format.
So portability is better than it was and not solved. Moving between tools still costs something, which is one of the things that has not settled in packaging.
What to actually do
Applications: commit the lock file. Always. This is what makes your deployment reproducible and what makes a colleague's environment match yours.
Libraries: do not commit a lock file as your dependency specification. A library declares ranges in pyproject.toml so it can coexist with other packages. A lock file in a library's repository is useful for CI and is not what consumers install against.
And regenerate deliberately. Updating the lock is an action with a diff to review, not something that happens as a side effect of installing.
Reading a lock diff
The habit that makes lock files useful rather than just present.
A one-line change is a package you updated.
A forty-line change from adding one dependency tells you what that dependency actually brings, which is information you did not have before and might act on.
A version dropping usually means a new constraint forced a downgrade somewhere, and that is worth understanding rather than accepting.
And a package appearing that you have never heard of is worth thirty seconds of checking. Adding a dependency is a decision and the lock diff is where its true size becomes visible.
The failure mode to avoid
A lock file that is committed and never regenerated.
Two years of pinned versions with known vulnerabilities, because the lock file was treated as a fixed artefact rather than as a snapshot with a date.
Locking pins the versions; it does not maintain them. The discipline is regular deliberate updates with the diff reviewed — which is a small recurring cost and the reason lock files are sometimes resented.
The short version
- A requirements file says what you asked for; a lock file says what you got, including transitive dependencies
- Without one, the same file installs different versions on different days
- A lock file has every resolved package at an exact version, with hashes and usually environment markers
- PEP 751 standardised
pylock.tomlandpip locklanded in pip 25.1; tool-specific formats are still not interchangeable - Commit it for applications, do not use it as a library's dependency specification, and regenerate deliberately
- Read the diff — a forty-line change from one added dependency tells you what that dependency really brings