The current standard way to do this is with a virtualenv and a requirements.txt file in which you specify the version of your dependencies, which you can then install with pip install -r requirements.txt.
Format is "packagename==0.1.0" (>=, < etc work too). VCS urls work too btw, e.g. "git+protocol://site/repo@tag_or_commit"
To get something similar (but inferior) to lockfiles you can use "pip freeze > filename"
Packaging/distribution is certainly a mess.
Zope and plone, the project(s) that came up with python eggs, used to have the concept of "known good sets"[1], which were massaged and used with zc.buildout :
http://www.buildout.org/en/latest/
I'm not sure I'd recommend it for greenfield projects - splitting things up in sane-sized chunks, and using the now in-standard-lib vevn (python3 -m venv) is probably a better idea.
But just for the record, there exists a canonical(ish) solution to some of these issues in python-land.
[1] http://grok.zope.org/doc/community/life_cycle/known_good_set...
The design was done by Phillip Eby, with funding from OSAF for the development of Chandler, not Zope.
Much shade has been thrown at eggs over the years, and people bemoan the warts, but eggs and setuptools were better than what we had at the time in Python.
Pretty simple, no? Most any python project I have seen in the last 5-6 years uses it.
We are using `pip-tools` to manage that: https://github.com/jazzband/pip-tools
If you have multiple sets of requirements (for dev vs testing vs production, eg), this becomes impractical real quick. You'll essentially have to tear down and rebuild your dependencies multiple times to update your reqs.