What’s wrong with scientific Python?

tl;dr: Although not perfect, Python is today one of the best platforms for scientific computing. It’s getting even better everyday thanks to the amazing work of a vibrant and growing community. I reviewed Python’s strengths in a previous post. Here, I cover the more sensitive issue of its weaknesses.

Don’t get me wrong. I love Python. I use it everyday. I think that, as of today, it is nothing less than one of the best platforms for high-performance scientific computing and data science. Not only among other competing open-source languages like R or Scilab; to me, it even outclasses commercial products like Matlab.

That being said, nobody’s perfect. While Python is mostly an excellent platform, it has some weaknesses. Sadly, those may prevent many people from jumping from Matlab & co to Python. I’d like to review them here. Note that these are my own opinions and many might disagree.

Update (24/01/2014): this updated version contains a few clarifications and additions compared to the original version from 15/01/2014. See also in the comments a very good summary and perspective by Konrad Hinsen.

A good general-purpose language, but not that good for scientific computing

Python has been around for more than twenty years. It was originally, and has always been, a general-purpose language. It is actually one of its greatest strengths compared to domain-specific languages designed primarily for scientific computing. For example, Matlab is really good with matrices, but not with other data structures. Many statisticians like R’s syntax, because it appears to do well what it is meant to do (statistics), but that’s basically it.

Today, if Python supports the data structures we often use in scientific computing, that’s thanks to NumPy. Whereas operations like indexing and slicing are quite natural (they translate to standard Python syntax), basic matrix manipulations like concatenations or reshaping feel a bit clunky. I think that’s more a limitation of the language than a limitation of NumPy. I imagine that many Matlab users trying to move to Python feel a bit disoriented because of this.

NumPy has a few weaknesses

A few things in NumPy are slightly odd and often confuse beginners. Off the top of my head:

  • To concatenate arrays with, say, hstack, you end up using double parentheses: hstack((a, b)). How are you explaining this to beginners?

  • Fancy indexing is slow: x[indices], where indices is an arbitrary array of integers, can be up to four times slower than Matlab. Now, if you use take(), you can achieve Matlab’s speed. If you don’t know this trick (and why would you, if you’re a Matlab user considering switching to Python?), you might just think that Python is four times slower than Matlab (this very situation actually happened to a colleague). Ouch…

  • Let’s create a matrix full of zeros: x = zeros((10, 10)). Now a random matrix: x = rand(10, 10). First case: you need double parentheses or you get an error. Second case, you need single parentheses or you get an error. Why?! Update: someone pointed out in the comments that there’s a really good reason for that: the rand(10, 10) function is a Matlab-friendly shortcut for numpy.random.random_sample((10, 10)). This little-known function is consistent with the rest of the NumPy API! This brings an additional point I’ll tackle below (about Python trying to be Matlab).

I’m sure we can find several other oddities. Now, I concede that these are all pretty minor things. NumPy remains an exceptionally useful library that has a huge importance in scientific computing.

It should be noted that Continuum Analytics is already working on the next generation of NumPy: Blaze, that looks fantastic. This is a brand new library, not just a new version of NumPy. I don’t think that the NumPy project itself is going to evolve a lot in the future, and I expect a lot more from Blaze.

Python is trying to be Matlab

(addition to the original version)

More precisely, the Python scientific stack (notably NumPy, SciPy, matplotlib…) tries to mimic some of the functionality and API of Matlab. This is actually not a bad idea: regular Matlab users have a chance to move to Python more easily. However, mimicking part of Matlab also means mimicking part of its weaknesses, including unpleasant or inconsistent API. The example of the Matlab shortcut rand(10, 10) being much more used than the original NumPy function numpy.random.random_sample((10, 10)) is interesting.

Think also about the habit that many Python users (and potentially ex-Matlab users like me!) have taken, which consists in putting from pylab import * at the top of a script, or running IPython in %pylab mode. I’ve done that for years (not anymore). The IPython core developers are desperately trying to prevent people from doing that now. The core of Matlab has no notion of namespaces: all function names are defined in a global, huge namespace. This is obviously not how Python is working, so the pylab mode imports everything in the interactive namespace. I understand that it makes life easier to people coming from Matlab, but that’s really not a good habit to take.

This is a double-edged sword: Python needs to depart from Matlab’s weaknesses, but it should also attract Matlab users with a very similar API. I’m not really sure what the best answer to this is.

CPython is slow

CPython is the mainstream implementation of the Python language. Being written in C, it can easily talk to rock-solid libraries that have been developed over the course of decades. CPython itself is a solid piece of software.

The problem is, it is not always very fast. Tight loops in particular tend to be slow. Even a trivial loop that does nothing wastes many CPU cycles in dynamic type checking, function calls, etc. This is why NumPy exists: matrix computations can be vectorized in C to make much better use of CPU cycles.

Cython is a popular solution, but I admit it sometimes feels a bit clunky. Mixing Python and C in some sort of bastard language seems more like a temporary hack to get things done than a long-term solution.

Just-in-time (JIT) compilation is one possibility to overcome this issue. PyPy uses this technique for general Python code, achieving impressive speed-ups. However, it seems like supporting NumPy and SciPy in PyPy is particularly difficult. There are some work in this direction but I’m not sure they will succeed in the foreseeable future.

A related project is Numba, by Continuum Analytics. This project is closely tied to Blaze. Its goal is to implement a JIT compiler specifically adapted to the kind of vectorized computations we have in scientific computing. It is also NumPy-aware. Numba is a very promising project that may overcome the most severe limitations of CPython in the context of scientific computing.

CPython does not like parallel computing

Another big issue in Python is its weak support of multicore processors. Once again, this limitation comes from CPython. The Global Interpreter Lock (GIL) is a mechanism in CPython that simplifies drastically memory management. It works by preventing threads, in a multithreaded Python interpreter, to run simultaneously. In other words, with CPython, you are stuck with one core per Python process.

There are some ways to run code in parallel with Python, but they are not particularly convenient. One can bypass the GIL with Cython or with C code, for example. Alternatively, one can simply use multiple processes. But multiprocessing is tedious and heavyweight in general, particularly on Python.

NumPy can run some specific types of computations (like matrix products) on multiple cores, thanks to libraries like BLAS. Blaze, the successor of NumPy, should support parallel computing (on CPU or GPU) out of the box.

Update. Some people have rightly pointed out that many, many solutions exist to do high-quality and efficient parallel computing in Python (see also Aron Ahmadia‘s comment below). These solutions notably include: mpi4py, petsc4py, IPython.parallel (which contains truly awesome stuff), joblib, among many others. Just look at this list. Some of these solutions are extremely powerful but may not be so easy to use, others are much simpler and already quite useful for embarrassingly parallel tasks (notably joblib).

IPython’s parallel computing features have a particularity here: not only are they deeply easy to used, they are also extremely powerful, notably when used with MPI. The learning curve that some might encounter comes more from MPI than IPython, in my opinion.

The very fact that so many people (including myself!) tried to improve the parallel computing capabilities of the Python platform is precisely the point I wanted to make. At its core, (C)Python is not well adapted to parallel computing. If it were, we wouldn’t see so many people attempting to fix it…

More generally, this also shows that Python’s native weaknesses can be fixed. And they are. They are even fixed so well that the very claim “Python does not like parallel computing” may hurt people’s feelings (sorry). Some of the solutions mentioned above are so well-established that they actually seem to belong to Python. But I think that’s forgetting the native limitations CPython.

Lack of scalable visualization tools

Python currently lacks a good interactive visualization tool that scales to huge datasets (tens of millions of points). Admitedly, there aren’t that many libraries in other languages either. But we’re working on it (shameless self-promotion).

Python 2 versus Python 3

This is a tough one. The vast majority of Pythonista scientists use an obsolete version: the 2.x branch. The maintained version (the 3.x branch) has been there for years. The problem is: it is not backward-compatible. Even the absolute simplest thing in Python 2 (print "Hello world!") is broken in Python 3.

The Python core developers have presented Python 3 as a new language that fixes many weaknesses of Python 2. This very claim might have frightened many people. What’s more, some respected Python developers are not that satisfied with the changes brought by Python 3. The situation looks really bad.

Fortunately, things tend to become slightly better with time. We now have a few ways to release packages working seamlessly in both branches, or even to have a single code base for both branches. Those slightly hackish techniques are now becoming standard. Releasing Python 2-only or Python 3-only new libraries is considered as bad practice. I think it will take a while before people just use Python 3 and forget Python 2 altogether.

As far as scientific packages is concerned, I would say that, as of today, we’re good. The vast majority of scientific libraries are compatible with Python 2 and Python 3. A Pythonista scientist can choose to work either with Python 2 or Python 3.

Of course, as time passes, using Python 3 instead of Python 2 for day-to-day work is more and more recommended. I have yet to do it, though. I guess we’re all a bit refractory to change. More importantly, most people (including me) don’t see much to be gained by making the switch. The benefit–cost ratio is probably still too weak. Non-scientist users (like Web developers) have probably more reasons to switch, be it only for Unicode support.

Package fragmentation

That’s the problem of distributed, open-source, self-organizing large projects like scientific Python. New packages are being developed in parallel. They get more mature, they get users. Then they make small, minor API changes here and there. Then package A v3 works with package B v7 but not v6. Or not v8. You start to get complicated dependencies between the packages you use in your software. Because the Python packaging system is so broken (see next paragraph), your users complain again and again.

I think this is hardly preventable with a young ecosystem like scientific Python. Hopefully things will become better with time.

The packaging system in Python

This is another tough one, probably even tougher than Python 3. In layman’s terms (might be a bit simplistic): packaging refers to the problem of letting other people use your Python library or program. You develop your software on a configuration A, and you want to share it with people on configurations B, C, and D. A configuration refers to a combination of an operating system (Windows? Unix? Linux? OS X? 32-bit or 64-bit?), a version of Python (2.x? 3.x? 32-bit or 64-bit?), the versions of your 47 Python dependencies, among others.

Solutions exist. They are all terrible. I won’t go into the details. Others have done it better than I could.

It’s even worse when it comes to Python programs with GUIs that you need to distribute to non-technical users (been there). You can’t expect them to have a Python distribution, so you need to somehow ship Python with your program. In my experience, it can be a nightmare. I have yet to find a good cross-platform solution that just works.

Once again, things are slowly getting better. The official solutions are improving. Also, Continuum Analytics appears to be doing some really good job in this respect, with their own conda/binstar system.

A glance at the future

I hope this post did not sound too much like a rant. It is not. I’m pretty happy with Python. I’m developing and maintaining software in Python. I even wrote a book on scientific Python! For sure, I don’t plan to leave this platform anytime soon. And, in any case, what I would leave it for? I don’t think there is a better alternative as of today.

Things could be better. They always can. And, they are getting better. I’m quite confident that scientific Python is going to be stronger and stronger in the years to come.

Will Python be the best open platform for scientific computing in 5 years? I’m quite sure it will. In 10 years? 15 years? Not so sure.

What will be the state of scientific computing in 10 years? Nobody can predict the future, but it’s fun to try (even if we’re totally wrong).

In 10 years, Python will probably still be around. However, maybe will it have been beaten by something else. I don’t know what this something else is going to be. Maybe Julia, maybe something else. Maybe Perl (just kidding).

This hypothetic language will be high-level, readable, easy to learn like Python, probably dynamic, maybe (partly) functional. But it won’t have the limitations of CPython. It will be fast. Just fast. Loops will be fast. It may implement a (LLVM-based?) JIT compiler. It will fully embrace parallel and heterogeneous architectures. It will be as good as Python, without its weaknesses. Or maybe I’m wrong and Python 5 (incompatible with Python 4) is going to be the winner.

Before I conclude, let me mention IPython. In 10 years, maybe will Python be stronger than ever. Or maybe will it have been replaced by another language. In any case, however, I think that IPython will be there for good. Maybe not IPython itself. But its legacy. The open, reproducible science paradigm. The idea of a notebook for scientific computing. Maybe even the very architecture of the notebook (including its JSON-based file format), which is actually already language-agnostic. Contrary to what its name suggests, IPython is not that tied to Python. Yes, it is entirely written in Python. But still, I think it is more than just a nice interactive thing on top of Python (I mostly think about the notebook, obviously).

So, what now? Should you switch to Python? If you’re happy with your existing platform, if you’re happy to pay thousands of dollars when alternative open-source solutions exist, or if you’re scared by the current weaknesses of Python, please don’t change a thing.

But if you do want to change, if you are well aware of Python’s limitations, if you are ready to face up to them (because solutions do exist), if you are willing to discover an amazing and vibrant platform, then take the plunge.

12 thoughts on “What’s wrong with scientific Python?

  1. It’s been a while since I’ve read anyone so casually making empty comments about the “best” language without any qualification.

    > I think that, as of today, it is nothing less than the best platform for high-performance scientific computing and data science. It is not only the best solution among other competing open-source languages like R or Scilab; it also outclasses commercial products like Matlab.

    Why is it the best solution? In what way does it “outclass”? Ideally don’t waste time on these kind of statements, there’s no “best solution” or “best platform” to data science at large.

    • I should have stated things clearer: the fact that it is the best is *subjective*. That’s my opinion, and others may disagree. I find it better than the other alternatives I tried, in terms of quality, flexibility, easy of use, support, documentation, etc. But there are also weaknesses.

  2. Excellent post Cyrille. I think we agree on almost everything except on the parallel computing angle. The mpi4py and petsc4py libraries by Lisandro Dalcin have enabled quite impressive use of Python at scale. As part of our work with PyClaw, we successfully scaled the implementation up to 65,536 processes with 90% parallel efficiency.

    A group of us are also working on ways to enhance reproducibility in the package management process with HashDist, which is a functional package manager targeting complex software stacks.


    • Thanks for your comment, you’re absolutely right. I was mainly referring to native (C)Python. Excellent third-party solutions like those you mention exist. They pretty much solve CPython’s weaknesses in parallel computing. I added a clarification in the post.

    • All these python parallelization systems are just so cumbersome (granted it’s a complex problem). They also don’t address the fact that single threaded CPython performance is pretty bad, Cython is a hack, and JIT optimizers like numba and parakeet are still a little touchy.

  3. > Let’s create a matrix full of zeros: x = zeros((10, 10)). Now a random matrix: x = rand(10, 10). First case: you need double parentheses or you get an error. Second case, you need single parentheses or you get an error. Why?!

    The first case makes a lot more sense when you do things like `a = np.random.rand(image.shape)`.

    The inconsistency with `rand()` is explained here:


    “rand() and randn() were added as conveniences for people who were used to the MATLAB functions. numpy.random.random_sample((2,3)) and numpy.random.standard_normal((2,3)) are the preferred, more consistent functions to use. Ignore rand() and randn() if you like.”

    However every tutorial and reference (at least that I’ve seen) uses rand(), so most people don’t know that random_sample() and standard_normal() exist.

  4. Much of what you describe can be summarized as “Python is 20 years old and wasn’t designed for science. The scientific ecosystem is the result of 20 years of organic evolution from this starting point”. Many symptoms you list are more or less direct consequences of these basic facts.

    On the other hand, those 20 years are also what made the scientific Python ecosystem so rich. And the reasons why scientists adopted Python 20 years ago are mostly still valid: it’s a relatively simple and elegant high-level language that lends itself to interfacing with C and Fortran.

    Looking towards the future, this leads to a couple of conclusions. First, younger languages (such as Julia) have a much smaller ecosystem but for the same reason a more consistent one. Over time they will probably evolve in the same way as Python. But is this inevitable? Could someone design a language for scientific computing that can escape the messiness syndrom? I am rather pessimistic about this, but I haven’t given it that much thought. I believe that messiness is the inevitable result of contributions from many people with different backgrounds and different priorities.

    Another question is how the scientific Python ecosystem will evolve in the future. Will the community continue to value backwards compatibility (i.e. CPython and Python 2) so much that it accepts to live with the band-aids that you describe? Or will it go for a modernized language and/or implementation (something like PyPy), at the cost of abandoning a part of its ecosystem?

    • I’m less pessimistic. The people designing the new things now know more mistakes to avoid, at least in broad strokes. look at package management in nodejs. nom avoided a lot of mistakes learned from python and ruby… and comparatively, it is much better. So far I have also been impressed with conda.

  5. Hi Cyrille,

    Which of your guidelines for the programming language of the future is Julia not already satisfying? Also, I heard a rumor yesterday that Python is looking to collaborate with Julia and incorporate some of the best Julia features in the near term. I think you mentioned one of the worst things about Python, e.g., hstack((A,B)), the non-user-friendly syntax and constant worries about dimensions matching.

    In Python, I’m always having to write assert statements to make sure the matrices are in the right dimension, and convert vectors of length K into vectors of size (K,1), irritating operations that make it very difficult for a beginner to write error-free codes. I spend more time debugging Python codes than writing them.

    I took a class on Python programming populated by University of Chicago graduate students. It took us two weeks and 6 hours of class time to get all 25 students running Python+NumPy+SciPy+iPython on their personal computers. Julia has all of those capabilities in 5mins, maximum. I have seen installation scare away many Python beginners.

    Also, I responded to your previous comment, here: http://www.jorgeluisgarcia.com/the-cobe-blog/2014/6/20/follow-up-to-my-comment-on-julia-vs-python

    All the best,

Leave a Reply

Your email address will not be published. Required fields are marked *

4 − = one

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>