I think you've hit the nail on the head; npm was the real killer app - it makes it very easy to try out node and result in a real, usable app with very little code nor (visible) complexity.
Don't know nowadays, but a few years back when I was still using python something that drove me nuts from pip was how bad it was at managing transitive dependencies.
If package A depends on package B 1.0 and package C also depends on package B but 2.0, the order in which you run pip install for A or C will lead to a different version of B. And of course either A or V would break as soon as they hit an incompatibility. Obviously you would notice that only by running the application or tests.
So you have to use pip freeze to kind of generate a semi lock file,and every project uses it in a different way, because you also need different dependencies files for dev and production....and these are just plain TXT files which I don't think it is the best "format" for this.
Then you have these text files but also a "setup.py" which is for the package you produce, but hey you need to read the txt because otherwise you need to maintain them in two places. So you add a Makefile to tie all of this together, and that's another rabbit hole.
Add to this the mess of virtualenvs already explained in sibling comments and you have the perfect storm.
I know that nowadays are things such as Poetry, which work a lot better, but there are a good bunch of tools competing with each other.
To make an analogy, npm feels like using the latest, top of the line Macbook while pip feels like using a Casio calculator from 1985 with an external keyboard. That's the gap I feel between these two tools.
So if you ever use python, try first poetry or any of the other tools, pip is a terrible mess.
Installs in a local folder to the project (node_modules) instead of pip which installs packages to the global python installation unless ran in a virtual env. So getting anything up and running is trivial, compared to the amount of installations and steps needed with pip, unless you wanna bork your global python installation.
With npm it's also really easy (almost too easy) to take your current folder and push it to npm, making the ecosystem bloom with packages.
Virtualenv used to be a third party package, which was a very high friction because you'll need to install it system-wide first (e.g. from distro's package manager) before you can use it to create virtual environments.
Not terrible, but still not as good and easy as npm. For example, you can't make a distinction between runtime and development dependencies, and it doesn't solve circular dependencies. Luckily there is Poetry which exactly does this.
It’s more of Python’s fault that pip’s, but (AFAICT) there isn’t a way to install multiple versions of the same dependency. You might have direct dependency A which needs version <2 of X, but also direct dependency B which needs version >=2 of X.
Since those version ranges of X don’t overlap, it isn’t possible to satisfy both A and B’s requirements. Node can handle this
I stand by the opinion that allowing multiple dependency versions was a major mistake that lead to both ballooning node_modules and abandoned packages getting caught up in your dependency tree just waiting for a malicious actor to hijack them.
In pip or composer, those dependencies have to be forked or removed or they will create conflicts.
Multiple dependency versions causes problems but the alternative is pretty bad, too. You can get stuck on older versions of a dependency because newer versions have a transitive dependency that causes a conflict with a different direct dependency
I feel like that's at least a solvable problem that makes itself obvious when the dependency is added. Multiple coexisting dependency versions sounds like a ticking time bomb to me.
Suppose I use a LibraryX that provides DataTypeX, and most of the functions in my project operate on DataTypeX. I later add another LibraryY, because it provides a utility function returning DataTypeX. I should be able to take the result and use it anywhere in my existing code. However, if my direct dependency on LibraryX is a different version than transitive dependency on LibraryX through LibraryY, then it may not have the same functionality. In trying to avoid a problem at compile-time, the package manager has introduced a huge potential problem at run-time.
Declarative dependency management. Every Node project has a package.json file, which is the first thing that I look at. It gives you so much information at a glance, and it allows for reproducible builds with just one command.
With pip you have to resort to hacky solutions like like pip freeze. It also doesn't solve circular dependencies. Luckily these days the Python world has Poetry, which exactly solves this issue, and might be even better than npm. Unfortunately it's third party and adoption is a bit slow, but I use it for all my projects.