This year I finished nine books, which is the fewest since 2014 (when I finished eight books). I say “finished” because I track the finishing dates of books in a tab-separated values file as I’ve a habit of starting a book and then switching to something else.

The total’s significantly lower than last year’s (15), which I think can be partly attributed to university, for which I’ve read bits and pieces but no books in full. Otherwise, 2019 has been a year of personal challenges. Here’s to smoother sailing in 2020!

Since there are so few, let’s look at all of them.

Narcoland, by Anabel Hernández

I read most of this in 2018 and finished it off in the first week of January. I’d had it for a while but picked it up after watching the Mexico season of Narcos (which, as with previous seasons, unsurprisingly treats the DEA very softly), and I raced through it. The depth of the collusion between the state and the cartels is astonishing. Hernández’s revelations will force you to seriously think about what’s really going on when you hear of wars between the gangs.

Mindset, by Carol S Dweck

Allison Kaptur talks about Mindset and Dweck’s findings in her Pycon talk Love your Bugs (which is great!). The lessons of studying the two mindsets in different fields are very interesting, as are the sections where Dweck discusses how the two mindsets can be induced and reinforced.

Code, by Charles Petzold

Code is easily one of my favourite books. It builds up a computer from simple pieces, and the journey is very fun. I first read it in 2013, and re-read it this year during my computer architecture & operating systems module. I will happily talk your ear off about how great it is, but you’re probably better served by reading the book itself.

Blackshirts and Reds, by Michael Parenti

Parenti starts with a look at how fascism serves the interests of capital, follows with a clear-eyed defence of communism and communist states (here’s a short video summary of his position), and spells out the disaster wrought by the restoration of capitalism in eastern Europe.

It’s well worth reading, and I’d highly recommend it to anyone drawn to left politics or curious about the Soviet Union.

The Century of Revolution, by Christopher Hill

Eric Hobsbawm, in his The Age of Revolution, which summarises the French and Industrial revolutions and their effects, talks about the way that bourgeois revolutions established support for domestic capitalism as the primary role of the state. He makes a sort-of off-hand comment about how the English revolution (or civil wars if you don’t like the R-word) had established that position here 150 years before France.

Hill’s book shows you how and why that happened. I’d recommend it.

Some snippets felt a bit alien as I had relatively little knowledge of the period — presumably a difference between being an adult in 1960 and 2019 — but it’s very readable and understandable.

Hill covers “the long 17th century” — 1603–1714 — and it can be quite detailed in parts. Verso have recently reissued his Reformation to Industrial Revolution, which covers a longer sweep (1530–1780) in fewer pages so presumably focuses on the core trends and events. I have a copy but have yet to read it. A quick glance suggests it doesn’t dig into the how or why of the Reformation, so you’ll want to look elsewhere if that’s something you’re interested in.

A History of Modern Computing (1e), by Paul E Ceruzzi

This is the first edition, written in the mid-1990s, so stops amid the personal computer era. There’s a second edition published in 2003, and Ceruzzi has written a concise history for MIT Press which is far, far shorter and should be considered entirely separately.

It’s one for the enthusiast, I’ll grant you, but I very much enjoyed this book. I was mostly interested in learning about early electronic computers, and here we start really with Eckert and Mauchly and the UNIVAC, through IBM, DEC and so on. Ceruzzi does a good job of explaining both the technical and business-military aspects of the computer industry’s development.

It’s very well written, and I’d recommend it to those interested in how we got from ENIAC and Colossus to here.

Mindhunter, by John Douglas and Mark Olshaker

The TV series is phenomenal, but it’s fictionalised to make for gripping viewing. John Douglas is the “real” Holden Ford, and his book details how the profiling programme got going and how it worked, and digs into more of the detail of profiling than you get in the show. There’s a good deal of colour and, strange enough given the horrific crimes, personal warmth in the book. Very interesting and readable.

Turing’s Cathedral, by George Dyson

The subtitle is “The Origins of the Digital Universe,” though if you really want that see Code for the technical aspects and Ceruzzi’s History for the story. The book does not live up to the subtitle and this seems to have irked some reviewers at Amazon and Goodreads. The IAS machine at the heart of it was not nearly the first digital computer (see this list) nor itself hugely significant in the history of computers. Ceruzzi refers to it as being the basis of the IBM 701 — John von Neumann, in charge of the IAS project, consulted for IBM — but otherwise it doesn’t feature in his book, save for an aside about one of the reports written as part of the project.

This, however, is to entirely miss the point.

Dyson has written an incredibly human book about the beginning of the computing age. While the book might appear to be about the computer built at the IAS, it is really about the people who were involved, what their ideas and concerns were and, maybe, what computers meant or would mean to them.

It’s a book about people and ideas, only tangentially about technology. Recommended.

Surveillance Valley, by Yasha Levine

I only came across Yasha Levine this year, and I believe it was about something “more” political.

I say “more” with the scare-quotes because the internet is political. Its creation was political, its existence is political, its ramifications for everyday life are political.

Surveillance Valley made me grasp that. Yasha’s quip that “the internet is a weapon” took me aback at first. It was created to serve US imperialism and still today serves US imperialism.

An inexact analogy: computers are a weapon. The IAS machine described in Turing’s Cathedral and its siblings were used to create weapons that threaten all life on Earth, and modern machines are used in killer robots and to damage civilian infrastructure.

That doesn’t capture the scope of Surveillance Valley, though. Ultimately it’s about the importance of taking political control over our societies, control currently held by capital and the state and military machinery it controls.

I recommend it to anyone — it’s incredibly well written and several sections made me laugh out loud, and its subject is more important than ever.

Reading resolutions for 2020

  • Read more books.

    It’s probably too much to aim for 31 (the number I read in 2015 and 2016), but I think 20 is probably achievable. My university project is due in by September 14, so after that I’ll have no excuse(!).

  • Buy fewer books.

    I… don’t really want to work out how many books I’ve bought this year. It’s more than nine. I’d quite like to buy no books “for pleasure” in 2020, given that I’ve got enough unread for several years. The same goes for books that I might class as technical-education but I don’t need to buy. I have enough for a long time.

  • Write summaries as I go.

    Writing this post has been enjoyable, but it’s done nothing to disprove my nagging feeling that writing posts on here takes hours every time.

Python’s zip function, which knits together two iterables, is indispensable for me. It works like this:

list_one = [1, 2, 3]
list_two = ["a", "b", "c"]
for pair in zip(list_one, list_two):
# (1, 'a')
# (2, 'b')
# (3, 'c')

If the two iterables differ in length, zip halts after the shortest is exhausted. If we add an additional element to one of the lists above, we get the same results:

list_one = [1, 2, 3]
list_two = ["a", "b", "c", "d"]  # Note extra item, "d"
for pair in zip(list_one, list_two):
# (1, 'a')
# (2, 'b')
# (3, 'c')

But the actual mechanics of this surprised me. Today I was working on the “chunked” problem from Python Morsels (which is great and you should totally try out if you write Python), and was left scratching my head after elements of my iterable started disappearing.

The basic problem for chunked is this: given some iterable, return its elements in count-length lists. Trey likes you to think in terms of “any iterable” so you can’t depend on list-like behaviour, such as being able to index into the iterable or check its length without consuming it.

It’s safer to assume you get one traversal. So, my solution starts like this, creating an iterator from the iterable.

def chunked(iterable, count):
    iterator = iter(iterable)

Then (eliding the scaffolding) I build up a new count-length chunk using zip in a comprehension:

temp = [item for item, _ in zip(iterator, range(count))]

Here I use the “earliest finish” behaviour of zip paired with range — the amount of numbers in the range (count-many of them) determines how many items I fetch from the iterator.

Let’s give this a try, using your imagination to flesh out the rest of chunked:

for chunk in chunked(iterable=range(10), count=4):
# [0, 1, 2, 3]
# [5, 6, 7, 8]

Er, hm. Not what I was expecting, which was:

# [0, 1, 2, 3]
# [4, 5, 6, 7]
# [8, 9]

Somehow, the program is consuming an extra item from iterator each time I create a chunk. But that list comprehension is the only place where I touch iterator. What gives?

Well, how does zip know when to terminate? If you take a look in the documentation, you’ll see a handy code sample that is “equivalent to” the implementation of zip. There we see that zip builds up a list of results by taking an item from each of the given iterables, but if any of those iterables are finished, it just returns — and discards the result list!

So what happens with zip(longer, shorter) is that it takes from longer, stashes the item, discovers shorter is exhausted, and discards the item from longer. And that’s what happens to the missing numbers in the example above.

This situation arises because I’m zipping the same iterable repeatedly, until it’s empty, and because the iterator is the first argument to zip. This small change works fine:

# Old, broken
temp = [item for item, _ in zip(iterator, range(count))]
# New, fixed
temp = [item for _, item in zip(range(count), iterator)]

In the new version, zip discovers that the iterator over the range is exhausted first, before it takes an item from iterator, so no items are ever discarded.

So, is this OK? Really, really not! This is super-fragile. It’s not obvious that switching the arguments will break the code. And really it just looks wrong, because surely the ignored tuple element (assigned to the underscore) should come after the item that we care about?

Thankfully, the itertools module has what we need (as always!). The reason I originally used the list comprehension-zip-range combo is because you can’t slice every iterable. For example:

(x**2 for x in range(10))[:4]
# ---------------------------------------------------------------------------
# TypeError                                 Traceback (most recent call last)
# <ipython-input-2-17f2a627cc7c> in <module>
# ----> 1 (x**2 for x in range(10))[:4]
# TypeError: 'generator' object is not subscriptable

But you can with islice:

list(islice((x**2 for x in range(10)), 4))
# [0, 1, 4, 9]

And this works great with iterators where you care about the current state:

to_10_sq = (x**2 for x in range(10))
list(islice(to_10_sq, 4))
# [0, 1, 4, 9]
list(islice(to_10_sq, 4))
# [16, 25, 36, 49]
list(islice(to_10_sq, 4))
# [64, 81]

Which leads us to the most straightforward way of building up those chunks.

chunk = list(islice(iterator, count))

(The chunks have to be “concrete” sequences as the problem requires some length-checking for one of the bonus parts, hence the list call.)

Thanks for reading. If I have some key messages, they’re these:

  • Python is lovely, but it’s not magic!
  • itertools might have solved your iteration problem already.
  • Check out Python Morsels. The problems are short, fun, and a nice way to improve your Python skills.

It looks like my 2011 iMac might be on the way out. I’ve been having odd graphical problems today and yesterday, and I think that it might be the graphics card overheating. Running Apple Diagnostics (Hardware Test as was) reports an error with the hard drive (the fan specifically) which I’ve seen before. My working theory at this point is that a failed or obstructed fan coupled with dust build-up and the fairly hot room has led to this point.

Photo of an iMac where part of the right-hand section of the screen image is displayed physically on the left

I’m going to get some compressed air and see if that helps matters at all, and then see if I can get it serviced. Unfortunately Apple now lists the 2011-model iMac as obsolete (or “vintage”) so we’ll see how that goes.

Funnily enough, this is not the original graphics card but a replacement installed by Apple when something similar (but not quite the same) happened several years ago, sorted out just before their replacement period ended (it was an acknowledged, somewhat widespread problem with the cards).

Honestly it’s happened at a bit of a naff time. The machine is otherwise fine, and it still feels incredibly fast (which I put down to the SSD). I was hoping it would last as long as many of the machines we have at work, almost all of which are from 2008 (despite what other press reports claim), though they do feel sluggish — particularly the couple that I (foolishly) upgraded past OS X 10.6. I was certainly not planning to replace it yet.

It would be an expense that I could do without too — having just bought a real chair and weird keyboard in anticipation of having to do much more work at home from October when I start an evening Masters in Computer Science at Birkbeck.

Which brings us neatly to the real annoyance about this: I was planning to buy a laptop to use on the course. I’ve bought a couple of bottom-end MacBook Airs for reporters at work, and they seem like decent enough machines. This idea was on the assumption that it would not be my main computer, so it could be less capable as I would be using it for focused tasks and leaving everything else — including much of the academic work of the course — to be done on my giant iMac at home.

But if the iMac isn’t in the picture anymore, what should I do? As I see it, I have two feasible options:

  • Buy a more capable laptop as my main computer.
  • Buy a basic laptop and a new iMac.

I spent a lot of money on the 27" iMac in October 2011, buying more than I needed really (partly because I was still playing computer games then). I wouldn’t replace it with something as high-end now as I know that I just don’t need the power, and I want to minimise the hit to my savings.

Part of me thinks that I should buy a MacBook Air now, according to plan but sooner than planned. Then I have something to tide me over other than a six-year-old iPad (running iOS 9!) and a five-year-old iPhone 5S, and then I can try to get the iMac fixed or replace it at a later date. (I’m also a bit anxious to buy the Air sooner rather than later, even though it’s still basically an old design, as I don’t really want to risk having to buy a laptop with a dodgy keyboard and only a couple of those odd USB-C sockets.)

Then the other option is to shelve completely the idea of buying another desktop and just buy a more powerful laptop. This appeals to me less, because I do want a bigger screen and I do want more storage.

I’ve been throwing the storage matter around in my head today and I can’t decide on a position. Backblaze tells me I have 530GB backed up, most of which is my iTunes library, so I don’t know if it’s a bit of a distraction — if I only had a laptop I’d have to keep it on an external drive at home, and is that so different from keeping it only in an iMac on my desk?

It’s difficult to know what to do. My gut feeling is to rush out and buy a basic Air immediately so as to not interrupt my life too much (particularly, I need to change jobs before the course starts as the hours don’t fit; this is not a secret to my boss or colleagues).

But my head says that I should see if I can crack on using my iPad for now, enquire about getting the iMac serviced as soon as possible, and then make a more considered decision at a later time.

It’s not a comfortable position for me, since this machine has been a fixture in my life for over six-and-a-half years. When the graphics card went last time, it was calming to know that it was a problem that Apple had committed to take care of (even if I almost missed the cutoff and had to drag the heavy thing all the way to Chafford Hundred on the train). No such luck this time.

(On a, er, positive note, I guess this will finally force me to work out a way of blogging from my iPad that isn’t as painful as I’m sure it will be to get this post up. [Later: It took some faffing about and too much manual work, but having (an old version of) Coda and shell access to my Linode server did the trick fine.])

My post detailing a Keyboard Maestro macro to open Jupyter notebooks had a dumb bug in the second shell pipeline, which fetches the URL of the desired notebook.

You’d hit it if:

  • You have more than one notebook server running.
  • The working directory of one is beneath another.
  • The subdirectory server was started more recently.
  • You tried to open the parent server with the macro.

The shorter path of the parent would match part of the child’s path.

The original grep pattern was:

grep "$KMVAR_dir"

And is now:

grep ":: $KMVAR_dir$"

So that it only matches the exact directory chosen in the list prompt, and not one of its children.

I’ve updated the Keyboard Maestro macro file too.

When I use images here, I tend to give ones without any transparency a border, which is done using CSS and applied to img tags unless they have a no-border class.

Like a good web citizen, I also specify image dimensions in HTML:

“The image’s rendered size is given in the width and height attributes, which allows the user agent to allocate space for the image before it is downloaded.”

In fact my BBEdit image snippet makes it a doddle:

<p <#* class="full-width"#>>
        alt="<#alt text#>"
        <#* class="no-border"#>

But this causes a problem, which I’ve spotted in a couple of my recent posts.

If you specify the image dimensions, and use a CSS border, and have your CSS box-sizing set to border-box, then the CSS border shrinks the amount of space available to the image to its specified dimensions − 2 × the border width.

So if you specify your img dimensions to match the dimensions of the file, then the image itself will be shrunk within the element.

This animation shows this situation, and what happens when you toggle the CSS border. Watch what happens to the image itself.

An animation showing an image being squeezed within the space it has been allocated, causing distortion.

(It’s got a slight offset from the text because it’s a screenshot of this blog and includes some of the background on each side.)

In contrast, this animation shows what happens when the dimensions are not specified, and so the image is free to grow when the border is applied:

An animation showing an image growing when a CSS border is applied, with no distortion to the image itself.

Really the culprit here is box-sizing: border-box, forcing the border to remain within the size of the img element itself. This is a behaviour you actually want, as it solves the old CSS problem of juggling widths, borders and padding within a parent element. Check out MDN’s box-sizing page to see what I mean.

What are my options, then?

  • Change box-sizing.

    I’m not touching this because the potential sizing headaches are not worth it, even just for img elements.

  • Apply a border to the image files themselves.

    No, because if I change my mind about the CSS, previously posted images are stuck with the old style forever. CSS borders should also work correctly across high-density displays, whereas a 1px border in the file may not.

  • Don’t specify dimensions in the HTML.

    I don’t like the idea of making pages of this site slower to render, but I think this is the least bad option, particularly given that this site is already pretty fast.

It’s not ideal, but that BBEdit snippet is now just:

<p <#* class="full-width"#>>
        alt="<#alt text#>"
        <#* class="no-border"#>

Hey, at least it makes images quicker to include in posts!