Ned Batchelder's blog Ned Batchelder's personal blog. en-US Ned Batchelder's blog Fall fallout 2022-07-30T10:57:03-04:00 Ned Batchelder More about my bike fall since I wrote about it two weeks ago.

  • I saw a neurosurgeon. He explained that I have a cavernoma, which is an anomalous collection of blood vessels with thin walls, which can lead to bleeding. The bleeding leaves behind iron deposits, which can cause a seizure. My cavernoma is located in a spot especially prone to seizures.
  • The neurosurgeon thought it would be a simple operation to remove the cavernoma, despite literally being brain surgery. He actually used the phrase “easy-peasy.” Also, his perspective was that this wasn’t a serious incident, since it wasn’t a stroke or death. I guess in his line of work, a seizure is on the small side.
  • Another perspective on severity: I took my bike to the shop to get it checked out. I was telling the bike dude about the crash. He said, “You didn’t need dental work? Then no big deal!”
  • I had an electroencephalogram (EEG). This involved having 27 wires pasted to my head and chest, then lying in a dark room with my eyes closed while they measured my brain activity. Toward the end, they placed a strobe light over my closed eyes, and flashed it at various frequencies. I realized, this is a black box unit test, and my brain is the system under test: provide some inputs, check the outputs, without being able to see the implementation. The initial report seemed to be, “nothing unusual,” but I have to check in with the neurologist.
  • After a few failed attempts, I managed to get the name of the person who called the police for me after my crash. I wrote to him, and he was very friendly, but didn’t have any more details about what happened. When he first saw me I was already on the ground, so he can’t explain the cause of the crash. Still, it felt good to connect with him and find out what he knew.

My energy level is not what it used to be, probably because of the Keppra (anti-seizure medication). Psychologically, I am not used to the idea that my brain can just shut off with no notice. I guess over time, I’ll just ignore that possibility?

The Fall 2022-07-13T15:44:07-04:00 Ned Batchelder One moment I was riding my bike; the next thing I remember, I was sitting on the ground talking to an EMT from the ambulance parked nearby.

This happened three weeks ago. I went to the emergency room, had a few CT scans and an MRI. The best theory is that I had a seizure. So now I am on anti-seizure medication, and am legally forbidden to drive a car for six months.

I was wearing a helmet, and was on a bike path, not a street. The physical effects were minimal: a sore shoulder and some road rash.

There was no eye-witness, so doctors guess I fell because I blacked out rather than the other way around. During the time I don’t remember, I called my wife and told her where I was, so maybe I was never truly unconscious? No one knows.

I usually have a low heart rate (a resting pulse of 50 bpm is not unusual), so maybe it was cardiac-related? I’m wearing a heart monitor to collect data.

The first week was hard because I felt completely limited in what I could do. All spring I had been feeling strong and capable in physical activities, and now I was finding a short walk difficult.

At first the anti-seizure meds made me tired and a bit fuzzy-headed. But we’ve adjusted them, and/or I’m adjusting to them, and/or my concussion is wearing off, so I feel more like myself. I’ve gotten back on the bike (though not alone), and have been swimming in the ocean, same as every summer.

I have more visits coming up with more doctors to try to understand what happened, and what might happen. I doubt they will be able to completely rule out a seizure, so I may be on meds for quite some time. Their recommendations are quite cautious (“Don’t take a bath without supervision”), so now we are making absurd trade-offs and considerations of risks and possibilities.

It’s unsettling to have lost time without a clear explanation, and especially unsettling to think that it could happen again at any time. I’m not sure what to do with that.

Math factoid of the day: 60 2022-06-16T06:53:00-04:00 Ned Batchelder 60 shows up in lots of places. It’s the smallest number divisible by 1 through 6, and perhaps because of that, it’s the basis of our timekeeping and angular measurements.

Of course the angles in an equilateral triangle are 60 degrees. But 60 also appears in solid geometry. There are four Archimedean solids (regular polyhedra made with any mixture of regular polygons) with 60 vertices. You can use Nat Alison’s beautiful polyhedra viewer to explore them:

400 walks 2022-06-14T05:43:52-04:00 Ned Batchelder Yesterday I did my 400th pandemic walk. These started as a way to get exercise during lockdown with my son Nat, as I wrote about in Pandemic walks (Feb 2021) and 300 walks (Sept 2021).

Now I’ve done 400 walks starting and ending at the same point, totaling 2318 miles:

All the streets I've walked

The cadence of these walks has slowed quite a bit. Not the pace of the walking itself, but the number of walks in a week. Nat is only with us one day a week now, so I don’t have to be out for his sake. I have biking and swimming as exercise options now, and my toes were getting a bit aggravated by walking.

Quantifying the walks was a fun project, and motivated me to get out and go, but it also pushed me to go farther and farther: in February, all the walks were longer than 7 miles, which might not have been wise. The problem with a statistic is the unreasonable expectation that it will constantly increase.

I’m not sure what will happen now. I’m going to continue walking as an at least occasional exercise, but maybe with a different motivator? Or maybe not: it’s hard to drop a project once you start it...

Now I’m going on a bike ride, and I’m not going to measure my speed.

Adding a dunder to an object 2022-06-05T10:29:00-04:00 Ned Batchelder We had a tricky debugging need at work: we wanted to track how an attribute on an object was changing. Here’s the unusual solution we used.

The __setattr__ special method (dunder) is called when an attribute is changed on an object. But like all special methods, it’s not found on the object, only on the class. We didn’t want to track changes to all objects of the class because it would produce too much noise in the logs.

So we wanted a per-object special method. The way we came up with was to define a new class with the special method. The class is derived from the object’s existing class, so that everything would work the way it should. Then we changed the object’s class.

Changing an object’s class sounds kind of impossible, but since in Python everything happens at run-time, you can just assign a new class to obj.__class__, and now that is the object’s class.

Here’s the code, simplified:

>>> class SomeObject:

...     ...

>>> class Nothing:
...     """Just to get a nice repr for Nothing."""
...     def __repr__(self):
...         return "<Nothing>"

>>> obj = SomeObject()
>>> obj.attr = "first"
>>> obj.attr

>>> def spy_on_changes(obj):
...     """Tweak an object to show attributes changing."""
...     class Wrapper(obj.__class__):
...         def __setattr__(self, name, value):
...             old = getattr(self, name, Nothing())
...             print(f"Spy: {name}{old!r} -> {value!r}")
...             return super().__setattr__(name, value)
...     obj.__class__ = Wrapper

>>> spy_on_changes(obj)
>>> obj.attr = "second"
Spy: attr: 'first' -> 'second'

>>> obj.attr

>>> obj.another = 'foo'
Spy: another: <Nothing> -> 'foo'

One more detail: the Nothing class lets us use repr() to show an object but also get a nice message if there wasn’t a value before.

The real code was more involved, and showed what code was changing the attribute. This is extreme, but helped us debug a problem. As I said in Machete-mode Debugging, Python’s dynamic nature can get us into trouble, so we might as well use it to get us out of trouble.

Custom search keywords 2022-05-26T07:25:47-04:00 Ned Batchelder You can define custom search keywords in your browser to make common searches easier. How to do it isn’t well documented, so I’m showing you how here.

As an example, I have a custom keyword “pypi”, so I can search PyPI for a package name by typing “pypi some name” in my browser’s address bar, and it takes me directly to name.

You can create your own custom keywords to have shortcuts to all kinds of searches. You provide a URL with a placeholder %s in it. When you type the keyword in the address bar, the rest of the entry is plugged into the placeholder, and you jump to the resulting URL.

Handy keywords

These are some of the custom search keywords I use. You can use them as-is, adapt them as you need, or create your own. Instructions for creating keywords are further down the page.

py: Search Python docs
pep: Find a PEP by number
pypi: Find a PyPI package
gh: GitHub code search
gpy: Google search for Python things, but not Monty Python
xlate: Translate text, auto-detecting the language

How to define a keyword in Firefox

Firefox defines custom keywords as bookmarks:

  1. Select Bookmarks - Manage Bookmarks from the menu to open the Library dialog.
  2. In the left-hand sidebar, select Quick Searches. You’ll see a handful of pre-defined searches.
  3. Using the gear drop-down at the top of the dialog, select Add Bookmark...
  4. Enter the URL of the search in the URL field. Use %s as the placeholder.
  5. Put the keyword you want to use in the Keyword field.
  6. You can give the bookmark a name to make it easier to find it later if you need to.
  7. Save the bookmark, and close the Library.
  8. Now you can use your keyword in the address bar.

How to define a keyword in Chrome

Chrome defines custom keywords as search engines in Settings:

  1. Select Preferences or type chrome://settings in the address bar.
  2. In the left-hand sidebar, select Search engine and then Manage search engines and site search.
  3. Scroll down to Site search.
  4. Click Add.
  5. Put a description in the Search engine field.
  6. Enter the URL in the URL field, with %s as a placeholder.
  7. Put the keyword in the Shortcut field.
  8. Click Add, then close the Settings tab.
  9. Now you can use your keyword in the address bar.
Cairo in Jupyter, better 2022-05-15T15:32:15-04:00 Ned Batchelder I finally came up with a way I like to create PyCairo drawings in a Jupyter notebook.

A few years ago I wrote here about how to draw Cairo SVG in a Jupyter notebook. That worked, but wasn’t as convenient as I wanted. Now I have a module that manages the PyCairo contexts for me. It automatically handles the displaying of SVG and PNG directly in the notebook, or lets me write them to a file.

The module is

The code looks like this (with a sample drawing copied from the PyCairo docs):

from drawing import cairo_context

def demo():
    with cairo_context(200, 200, format="svg") as context:
        x, y, x1, y1 = 0.1, 0.5, 0.4, 0.9
        x2, y2, x3, y3 = 0.6, 0.1, 0.9, 0.5
        context.scale(200, 200)
        context.move_to(x, y)
        context.curve_to(x1, y1, x2, y2, x3, y3)
        context.set_source_rgba(1, 0.2, 0.2, 0.6)
        context.move_to(x, y)
        context.line_to(x1, y1)
        context.move_to(x2, y2)
        context.line_to(x3, y3)
    return context


Using demo() in a notebook cell will draw the SVG. Nice.

The key to making this work is Jupyter’s special methods _repr_svg_, _repr_png_, and a little _repr_html_ thrown in also.

The code is at I created it so that I could play around with Truchet tiles:

A multi-scale Truchet tiling
Twitter tidbits 2022-05-08T08:34:11-04:00 Ned Batchelder Lately I’ve been posting Python tidbits on Twitter. It’s been fun finding things that people might not yet know, fitting them into a tweet, and giving them some energy.

People ask how I make them. I tried a few “make a code screenshot” sites at first, but wanted more control over how the images looked. Now I create the code shots in vim with a tweaked color scheme (shades-of-purple). This lets me do things like highlight specific words I want to draw attention to. Then I use the Mac screenshot tool to grab an image:

An example of purpled code

The font is Recursive Mono SemiCasual. Recursive is a lovely and lively variable font with a number of axes to play with, especially the Casual axis, which I’ve set to halfway between Linear and Casual. You can get it now on Google Fonts. I’d like to try using Semi-Mono, but vim only understands monospaced fonts.

A sample of Recursive Mono, in Linear, SemiCasual, and Casual

Some people have asked about publishing these tidbits here on also, but I’m not sure it’s worth the effort. Would it be helpful to have a page like this that collected them per-month or something? Or one tweet per page? Or just let them live on Twitter?

Python custom formatting 2022-04-14T19:35:54-04:00 Ned Batchelder Python f-strings use a formatting mini-language, the same as the older .format() function. After the colon comes short specifications for how to format the value:

>>> word = "Hello"

>>> f"{word:/^20}"
>>> amt = 12345678
>> f"{amt:20,}"
'          12,345,678'

Datetimes can use strftime syntax:

>>> f"{now:%Y-%m on day %d}"

'2022-04 on day 14'

The reason datetime uses different formatting specs than strings is because datetime defines its own __format__ method. Any object can define its own formatting mini-language. F-strings and .format() will use the __format__ method on an object, and pass it the formatting directives being used:

>>> class Confused:

...     def __format__(self, fmt):
...         return f"What is {fmt}?"
>>> c = Confused()
>>> f"{c:xyz12}"
'What is xyz12?'

Of course, __format__ can be used for more useful formatting than Confused is doing...

Geographic latitude and longitude are conventionally presented in a few different formats: degrees; or degrees and minutes; or degrees, minutes and seconds. Then the numbers can have varying number of decimal places, and sometimes the units are represented by symbols.

Here’s an implementation of those possibilities in __format__. The format string starts with “d”, “dm”, or “dms” to indicate the basic format. The number of decimal places can be specified with “.N”. Finally, symbols can be added, either plain or fancy, by adding a quote or minute symbol:

import dataclasses, re

class LatLong:
    lat: float
    long: float

    def __format__(self, fmt):
        dms, nfmt, opts = re.fullmatch(r"(dm?s?)([.\d]*)([′']?)", fmt).groups()
        formatted = []
        for num in [, self.long]:
            parts = []
            for ms in dms[1:]:
                num = abs((num - int(num)) * 60)
            parts.append(format(num, nfmt + "f"))
            syms = None
            if "'" in opts:
                syms = "°'\""
            elif "′" in opts:
                syms = "°′″"
            if opts:
                parts = [p + s for p, s in zip(parts, syms)]
            formatted.append(" ".join(parts))
        joined = ", ".join(formatted)
        return joined
>>> where = LatLong(42.359764937, -71.092068768)

>>> print(f"Location: {where:d'}")
Location: 42.359765°, -71.092069°
>>> print(f"Location: {where:d.4}")
Location: 42.3598, -71.0921
>>> print(f"Location: {where:dm'}")
Location: 42° 21.585896', -71° 5.524126'
>>> print(f"Location: {where:dms.4'}")
Location: 42° 21' 35.1538", -71° 5' 31.4476"
>>> print(f"Location: {where:dms.4}")
Location: 42 21 35.1538, -71 5 31.4476
>>> print(f"Location: {where:dms.6′}")
Location: 42° 21 35.153773, -71° 5 31.447565
>>> print("There: {:dms.6′}".format(where))
There: 42° 21 35.153773, -71° 5 31.447565
>>> print(format(where, "dms.6′"))
42° 21 35.153773, -71° 5 31.447565

This implementation doesn’t handle errors properly, but shows the basic idea. Also, lat/long are often shown with N/S E/W instead of positive and negative values. That’s left as an exercise for the reader.

Singleton is a bad idea 2022-04-10T06:25:46-04:00 Ned Batchelder Design patterns are a great way to think about interactions among classes. But the classic Singleton pattern is bad: you shouldn’t use it and there are better options.

The classic Singleton pattern is a class which always gives you the same object when you create an instance of the class. It’s used to ensure that all users of a class are using the same object.

The first problem with Singleton is that it encourages you to mix together two different ideas into one class. The first idea is whatever your class is about. Let’s say you are writing a chess game. Your ChessBoard class should only be concerned with chess-board-ness. A separate second idea is that your program only needs one board. That’s not a fact intrinsic to chess boards, so it shouldn’t be coded into your ChessBoard class.

If your program only needs one of a certain object, you can just make one:

class ChessBoard:

    def __init__(self):

the_chess_board = ChessBoard()

If you want centralized management of the instance, use a function to manage a global:

_the_chess_board = None

def the_chess_board():
    global _the_chess_board
    if _the_chess_board is None:
        _the_chess_board = ChessBoard()
    return _the_chess_board

Or let functools do it for you:

@functools.cache        # new in 3.9

def the_chess_board():
    return ChessBoard()

If you still really want the class to manage a single instance, you can shift that function to be a classmethod:

class ChessBoard:

    def __init__(self):

    def the_board(cls):
        return cls()

These ways are simpler than the usual gymnastics to implement singletons. They have the additional benefit that the users of the class clearly understand that they are accessing a global instance. This is another problem with Singleton: it gives you a class that lies. Calling the class looks like you are making new objects, but you are not.

These ways also let you still make new instances when you need to. Your ChessBoard tests will need to create many instances to keep your tests isolated from each other. The Singleton pattern makes this difficult and requires even more tricky machinations.

But I just need one!

So just make one. There’s no reason to complicate the class by adding Singleton enforcement to it.

But I need it everywhere in my code!

OK, use the_chess_board() wherever you were using ChessBoard().

But I still want a way to enforce “just one!”

The function manages the global instance. Why does it have to happen inside the class? You should separate the concept of what the class is from the idea that there should be only one.

But someone might make more instances!

Who? Document the right way to use the class. Make it clear and easy to do it the right way, and then let it be. You can’t defend against every bug, and it’s their program anyway.

But I thought globals were bad?

They are bad, but your Singleton was also a global: there was only one for the whole process, and it could be changed from anywhere. It wasn’t literally a Python global variable, but it had all the same bad qualities, just hidden behind some tricky meta-programming. If you’re going to have a global, be up-front about it.

But what about None and things like it?

True, some immutable value types can be singletons, but that’s not how people use the Singleton pattern, and how often are you writing a class like None?

But Singleton is in the Design Patterns book!

That doesn’t mean it’s a good idea. None of the examples in the book are true Singletons, they are all examples of programs that just happen to need only one instance. And the Design Patterns book was never meant to be prescriptive: it’s not telling you what to do, it’s describing what people have done.