Ned Batchelder's blog Ned Batchelder's personal blog. en-US Ned Batchelder's blog Real Django site 2021-09-13T06:37:23-04:00 Ned Batchelder Big changes behind the scenes here at, but only a small change for you.

My hosting provider was being acquired, and they said they would migrate my site to the new host. Then they wrote last month to say they couldn’t migrate it (no word why), and that I had six weeks to find a new home.

I briefly tried to just move the site as it was, but PHP 5 was in the mix. Rather than learn how to move it to PHP 7, I bit the bullet and converted it to a real Django-served site.

For 13 years this site has been built with Django, but served as static HTML pages. The comments were handled by PHP code. As part of this move, the site is now served directly by Django on the host, with Django-implemented comments.

This should all be invisible to readers of the site, except for one thing: comments are now written as Markdown instead of as neutered HTML. Having a Django foundation means I will be able to make changes more easily in the future.

Behind the scenes, there is still plenty of strange tech: content is in XML, loaded into a SQLite database locally, then rsync’ed to the server.

Some dormant areas of the site aren’t serving properly yet, but the important stuff works. If you see a problem, please let me know.

Me on Bug Hunters Café 2021-08-23T11:19:13-04:00 Ned Batchelder I was a guest on the Bug Hunters Café podcast: episode #12, The Café Within.

Bug Hunters Café is a fun open-ended conversation about bugs and other programming topics, hosted by Jason C McDonald and Bojan Miletić. It’s whimsically set in a science-fiction-themed café.

We talked about a bunch of things: testing,, printers, abstractions, Python’s unfortunate readability, kudzu, IRC, Python Discord,, sitting up straight, asking and answering questions, yes and no, studying data structures, singletons, and more.

It was great to have an extended discussion, and it was fun to play along with the café setting.

Pythonic monotonic 2021-08-12T19:03:07-04:00 Ned Batchelder In a recent conversation, someone shared some code from a book about technical job interviews. They wanted to know if I agreed that the code was “Pythonic.”

The problem was to find the runs of increasing and decreasing values in a list, and to produce a sequence of the runs, but to reverse the decreasing runs, so that they are also increasing. This was the “Pythonic” code:

import itertools

def mono_runs_pythonic(seq):
    class Monotonic:
        def __init__(self):
            self._last = float("-inf")

        def __call__(self, curr):
            res = curr < self._last
            self._last = curr
            return res

    return [
        list(group)[::-1 if is_decreasing else 1]
        for is_decreasing, group in itertools.groupby(seq, Monotonic())

mono_runs_pythonic([1, 2, 3, 2, 1, 4, 5, 6, 7])
# --> [1, 2, 3], [1, 2], [4, 5, 6, 7]

My first response was that I don’t like this code, because I had to read it with my eyebrows. That is, I furrow my brow, and read slowly, and scowl at the code as I puzzle through it. This code is dense and tricky.

Is it Pythonic? I guess in the sense that it uses a number of Python-specific constructs and tools, yes. But not in the sense of Python code being clear and straightforward. It uses Python thoroughly, but misses the spirit.

I tried my hand at my own solution. It came out like this:

def mono_runs_simpler(seq):

    seqit = iter(seq)
    run = [next(seqit)]
    up = True
    for v in seqit:
        good = (v > run[-1]) if up else (v < run[-1])
        if good:
            yield run if up else run[::-1]
            run = [v]
            up = not up
    if run:
        yield run

This code also uses some unusual Python techniques, but is clearer to me. I’m not sure everyone would agree it is clearer. Maybe you have an even better way to do it.

Aside from the question of which code is better, I also didn’t like that this code was presented as a good solution for a job interview. Studying code like this to learn intricate tricks of Python is not a good way to get a job. Or, it might be a good way to get a job, but I don’t like that it might work. Job interviews should be about much deeper concerns than whether you know little-visited corners of the Python standard library.

Aptus v3 2021-07-25T19:19:43-04:00 Ned Batchelder After a hiatus of almost 13 years, I’ve made a new release of Aptus, my Mandelbrot explorer. I got re-interested in it as a way to make Zoom backgrounds during the pandemic.

I started by moving it from Python 2 to Python 3.9. Then the wxPython GUI needed to move up a few versions, but it wasn’t working too well. So I built a new browser-based user interface. A compute server in FastAPI and a UI in vanilla JavaScript are the new GUI.

There are features from the old GUI that aren’t available yet (You Are Here is my favorite), but it’s very usable.

Intricate Mandelbrot

Coverage 6.0 beta 1 2021-07-18T16:30:50-04:00 Ned Batchelder I’ve just published 6.0 beta 1. The latest changes are not monumental, but I would love for you to test it.

The version bump to 6.0 is because I’ve dropped support for Python 2 and Python 3.5. But also because the changes to how third-party code is handled felt potentially disruptive. Please read that blog post for details.

The other big thing happening with is Python 3.10. Because of PEP 626 (“Precise line numbers for debugging and other tools”), there have been many changes to how Python reports line numbers. depends on those line numbers, so there have been more than a few bug reports written against Python as the work has progressed.

It will be important to test with 3.10, but to be fair, there have already been a few problems reported in the latest version, 3.10 beta 4. So if you use beta 4, you’ll want to avoid re-reporting the known problems:

If you can build 3.10 from source, that would be a great thing to use for testing, or get ready to jump on 3.10.0 rc1 when it comes out on August 2.


Math factoid of the day: 59 icosahedra 2021-06-16T05:53:00-04:00 Ned Batchelder Today’s math factoid of the day: there are 59 icosahedra.

I’ve long been fascinated by stellation, the process of extending a polyhedron’s faces to form new structures. The word stellation is from the Latin root for star, so it means to make star-like. The star that graces the upper-left corner of this site is a stellated dodecahedron.

To understand stellations, let’s start with two dimensions. Consider a regular pentagon. If we extend the sides of the pentagon, they will intersect again, making a five-pointed star:

Pentagon extending to become a five-pointed star

(image from

Looked at this way, the original pentagon is just the first 2D area enclosed by the sides’ five infinite lines. When we extend the sides, we’re finding five more areas (the points) enclosed by those same five lines. The five-pointed star is a stellation of the pentagon.

With a pentagon, there are no more stellations: extending the sides even more won’t find any more intersections, so no new areas are enclosed. But more complex shapes can have more stellations. As an example, a 9-sided polygon will have three stellations in sequence:

Three stellations of a 9-sided polygon

(image by Tomruen, CC BY-SA 3.0, via Wikimedia Commons.)

Going to three dimensions, stellating a polyhedron doesn’t extend edges, it extends faces. Here’s a video my son Ben made showing a dodecahedron producing three stellations. The faces each lie in one of 12 infinite planes, which intersect in a few different ways, producing new shapes:

The 12-sided dodecahedron only has those three stellations. Extending the planes further doesn’t produce any more intersections, so no new volumes are enclosed. The star on this site is the last (third) stellation of the dodecahedron.

What about the 20-sided icosahedron, how many stellations does it have? Things get much more complicated. In 1938, H.S.M. Coxeter and his co-authors wrote a famous book about the enumeration of the icosahedron’s stellations. First, rules had to be considered about what would count as a stellation, and what would not.

Like the 9-sided polygon, the dodecahedron has a simple linear sequence of stellations, each building on the previous, like nested Russian dolls. But the icosahedron is not so tidy. The book describes intricate rules that I won’t get into for determining what would be considered a stellation.

They counted 58 stellations of the icosahedron. When you include the original icosahedron itself in the list, you get the title of the book: The Fifty-Nine Icosahedra.

Jan Albert Vroegop has a beautiful gallery of all 59, but here is a sampling (from Maurice Starck’s page):

10 of the 59 stellations

BTW, you can buy 3D-printed stellations, but trigger warning: some of them are called icosahedra when they are really dodecahedra!

Goodbye Freenode 2021-06-13T06:39:07-04:00 Ned Batchelder For the last month or so, the IRC world has been embroiled in drama over the new ownership of Freenode. For me, it culminated yesterday when I was banned from Freenode.

I’m not going to try to recap what happened in detail, but I can give you my overall perspective on it. The new owners started on the wrong foot, and then mishandled every subsequent interaction. At every turn, people feared the new owners and staff were going to do something malicious. Then something bad would happen, people would say, “look: malice!,” and the new staff would say, “it wasn’t malice, it was a mistake!” Then it would happen again.

A month ago, when the new trends were becoming clear, the operators of the #python channel (including me) decided to move #python to the new network being run by the old Freenode staff. But we also stayed in the Freenode channel to let people know where everyone had gone.

Yesterday, after a heated debate in the Freenode channel where I was accused of splitting the community, I got k-lined (banned entirely from Freenode). The reason given was “spamming”, because of my recurring message about the move to Libera. Then the entire Freenode #python channel was closed. So much for caring about the community.

Was it malice or was it mistake? Does it matter? It’s not a good way to run a network. After the channel was closed, people asking staff about what happened were banned from asking. That wasn’t a mistake.

I can’t claim to know the minds of the new Freenode owners or staff. All I can do is see their actions, or I could until they banned me from Freenode. I know that some of the new staff are people we had come to know over the years as persistent disrupters in #python. The people advocating for the new Freenode staff seem to trend towards the anti-code-of-conduct, “free speech means I don’t have to care” cohort. And the new staff seems to be using force to silence people asking questions. It’s clear that transparency is not a strong value for them.

Setting aside network drama, the big picture here is that the Freenode #python community isn’t split: it’s alive and well. It’s just not on Freenode anymore, it’s on Libera.

Freenode was a good thing. But the domain name of the server was the least important part of it, just a piece of technical trivia. There’s no reason to stick with Freenode just because it is called Freenode. As with any way of bringing people together, the important part is the people. If all of the people go someplace else, follow them there, and continue.

See you on Libera.

What’s in which Python 3.7–3.9? 2021-05-17T19:04:00-04:00 Ned Batchelder This is the fourth in a series of summarizations of what’s in each release of Python. The first three were:

3.7: June 27, 2018

  • postponed evaluation of type annotations (PEP 563)
  • async and await are keywords
  • dataclasses
  • dict order is guaranteed

Full list of 3.7 changes.

3.8: October 14, 2019

  • assignment expressions (walrus operator := )
  • f-string “=” specifier
  • positional-only parameters

Full list of 3.8 changes.

3.9: October 5, 2020

  • dict union operators
  • type hinting generics in standard collections
  • relaxed decorator syntax
  • str.removeprefix and str.removesuffix

Full list of 3.9 changes.

Cherry-picking a pull request 2021-05-16T15:45:04-04:00 Ned Batchelder At work, we work in GitHub pull requests that get merged to the main branch. We also have twice-yearly community release branches, and a small fraction of the main-branch changes need to be copied onto the current release branch. Trying to automate choosing the commits to cherry-pick lead me into some Git and GitHub complexities.

Git has three different ways to finish up a pull request, which complicates the process of figuring out what to cherry-pick. Before getting into cherry-picking, let’s look at the three finishes to pull requests. Suppose we have four commits on the main branch (A-B-C-D), and a pull request for a feature branch started from B with two commits (F-G) on it:


The F-G pull request can be brought into the main branch in three ways. First, the F-G commits can be merged to main with a merge commit:


Second, the two commits can be rebased onto main as two new commits Fr-Gr (for F-rebased and G-rebased):


Lastly, the two commits can be squashed down to one new commit FGs (for F and G squashed):


Note that for rebased and squashed pull requests, the original commits F-G will not be reachable from the main branch, and will eventually disappear from the repo, indicated by their dashed outlines.

Now let’s consider the release branch. This is a branch made twice a year to mark community releases of the platform. Once the branch is made, some fixes need to be cherry-picked onto it from the main branch. We can’t just merge the fixes, because that would bring the entire history of the main branch into the release. Cherry-picking lets us take just the commits we want.

As an example, here E has been cherry-picked as Ec:


The question now is:

To get the changes from a finished pull request onto the release branch, what commits should we cherry-pick?

The two rules are:

  1. The commits should make the same change to the release branch that were made to the main branch, and
  2. The commits should be reachable from the main branch, in case we need to later investigate how the changes came to be.

GitHub doesn’t record what approach was used to finish a pull request (unless I’ve missed something). It records what it calls the “merge commit”. For merged pull request, this is the actual merge commit. For rebased and squashed pull requests, it’s the final commit that ended up on the main branch.

In the case of a merged pull request, the answer is easy: cherry-pick the two original commits in the pull request. We can tell the pull request was merged because the merge commit (with a thicker outline) has two parents (it’s actually a merge):


But for rebased and squashed pull requests, the answer is not so simple. We can tell the pull request wasn’t merged, because the recorded “merge commit” isn’t a merge. Somehow we have to figure out how many commits starting with the merge commit are the right ones to take. For a rebased pull request we’d like to cherry-pick as many commits as the pull request had:


And for a squashed pull request, we want to cherry-pick just the one squashed commit:


But how to tell the difference between these two situations? I don’t know the best approach. Maybe comparing the commit messages? My first way was to look at the count of added and deleted lines. If the merge commit changes as many lines as the pull request as a whole, then just take that one commit. But that could be wrong if a rebased pull request had overlapping commits, and the last commit changed all the lines.

Is there some bit of information I’ve overlooked? Does git or GitHub have a way to unambiguously distinguish these cases?

Shazam for fractals? 2021-04-19T13:46:00-04:00 Ned Batchelder Is there any way to find the coordinates of a Mandelbrot image from the image? Even a guess as to the rough neighborhood?

I recently saw this as someone’s avatar:

A portion of the Mandelbrot fractal, with interesting features

This is clearly the Mandelbrot fractal, but where is it? What coordinates and magnification? Without accompanying information, is it possible to find it? I’d like to explore that region, but how can I find it?

This problem reminds me of Shazam, the seemingly magical app that listens to what’s playing in your environment, and tells you what song it is.

Is there any way?

BTW, the way I solved this problem in my own long-neglected Mandelbrot explorer Aptus is to write data records into the PNG files it produces.

For example, you can download the image from the Aptus page, and use imagemagick to see what data it contains:

$ identify -verbose JamesGiantPeach_med.png
  Filename: JamesGiantPeach_med.png
  Format: PNG (Portable Network Graphics)
    Aptus State:
    "Aptus State": 1,
    "angle": 0.0,
    "center": [-1.8605327723759248, -1.270334865601334e-05],
    "continuous": true,
    "diam": [1.788139343261719e-07, 1.788139343261719e-07],
    "iter_limit": 999,
    "mode": "mandelbrot",
    "palette": [
        ["spectrum", {"l": [50, 150], "ncolors": 12}],
        ["stretch", {"hsl": true, "steps": 25}]
    "palette_phase": 190,
    "palette_scale": 1.0,
    "size": [500, 370],
    "supersample": 3
    Software: Aptus 2.0

To prove it works, here is the same place with a different viewer, using a URL crafted from the data in the PNG.

Aptus also knows how to read these files, so you can open a PNG it produced, and you will be exploring where it was captured. It’s like jumping into a photo to visit the place it was taken. I used the same technique in Flourish.

Too bad more images don’t carry metadata to help you re-find their location in mathematical space.