Dark mode, continued

Sunday 19 July 2020

I’ve continued to work on the dark mode for this site. After playing around with different approaches, and learning more about Sass, I have something I like.

I wanted the site to respect the user’s OS settings, but I also wanted a control that would switch to the other mode. A common CSS technique is to put a class on the <html> tag, and then have the CSS use that class to choose styling. Media queries would get me the user’s preference, and an “html.othermode” class would be used to override the media query.

The Sass I came up with looks like this:

@mixin lightmode {
    @content;
    @media (prefers-color-scheme: dark) {
        html.othermode & {
            @content;
        }
    }
}

@mixin darkmode {
    @media (prefers-color-scheme: dark) {
        @content;
    }
    @media (prefers-color-scheme: light) {
        html.othermode & {
            @content;
        }
    }
}

These mixins let me declare the light- or dark-mode properties just once:

body {
    @include lightmode {
        --bg-color: white;
        --fg-color: black;
        --tint-border: #00000080;
    }

    @include darkmode {
        --bg-color: #1F1F1F;
        --fg-color: #eeeeee;
        --tint-border: #ffffff66;
    }
}

The lightmode mixin uses its properties as the default, and also as the “othermode” settings for users who prefer dark mode. The darkmode mixin uses its properties for dark mode, and also for the “othermode” settings for users who prefer light mode. The Sass mixin syntax lets me get that without having to repeat any property settings.

One important rule though: you have to always use the mixins together, or you will have defined only half the settings you need.

Coupled with a light/dark switcher (look at the bottom of this page, in the footer), now the user can choose what look they want.

A few other points:

  • I learned that media queries do not change the CSS specificity. Originally, my mixins were more symmetric: darkmode didn’t use a light media query. But they didn’t work right because the darkmode “othermode” properties came later in the file than the lightmode prefers-dark “othermode” properties. So a prefers-dark user would still get dark properties even when othermode was in effect. (I know: that explanation is confusing, but trust me, it makes sense!)
  • Many examples of CSS variables show them being defined on “--root”, but I don’t see the advantage over defining them on <body>. The <body> tag is more convenient because it has “html.othermode” as a possible ancestor, letting me use my mixins to control the styling.
  • I’ve tweaked some colors since last week when I first rolled this out: dark-mode links are now yellow instead of blue.
  • Sass is remarkably good at providing just the right kinds of abstraction for compiling CSS.

Dark mode

Monday 13 July 2020

Recently I released Coverage.py 5.2, which included a dark-mode for the HTML report, contributed by Vince Salvino. I hadn’t seen a dark-mode implementation for HTML before, so it piqued my interest.

Now I’ve implemented a dark mode for this site. There is no manual control for it, it uses the “prefers-color-scheme” CSS media query to follow the current operating system setting. If you use a Mac in dark mode, Chrome or Safari should show you this site in dark mode.

Implementing it was fun, as fiddling with this site always is. It’s a good chance to learn new things. In this case, CSS variables were new to me.

The article “prefers-color-scheme: Hello darkness, my old friend” by Thomas Steiner was very helpful. Luckily, for this simple site I could skip over many parts of Thomas’ comprehensive treatment of the considerations.

One of the trickier things in dark mode is adjusting color palettes, but this site pretty much has no colors, so that wasn’t a problem. I did have to be careful to keep the text at sufficiently high contrast ratios.

Here’s how it looks in light mode (the classic, unchanged):

This site, in light mode

And in dark mode:

This site, in dark mode

A big challenge was re-rendering the star in the upper left. I’ll have to switch from POV-Ray to Blender one of these days...

I don’t use dark mode myself, so I’m not sure if I got the vibe right. Let me know if there’s something I should adjust.

2500

Sunday 28 June 2020

This is the 2500th post on this blog. That’s a lot of writing. I estimate this site has about 480,000 words in total, enough for five books.

I’ve been writing here for more than 18 years. The pace is different than when I started: last year I wrote 33 posts. Compare that to 2003, when I wrote more than ten times as many: 441 posts! Twitter has siphoned off some of the short-post energy, but also interests shift over time.

Writing is a good way to understand things, and to learn things. People mostly think of writing as a way to teach and explain, and I am glad when my posts can do that. But I also really value the feedback loop of learning as I explain, and the deeper understanding I find when I teach.

Here’s a common piece of advice from people who create things: to make better things, make more things. Not only does it give you constant practice at making things, but it gives you more chances at lucking into making a good thing.

These days I set myself a goal of writing two posts a month. I find the goal helpful. It prods me to dig for topics. Some will be duds, but sometimes an apparently boring idea will turn out well.

I can’t promise everything (or anything!) will be interesting or insightful. But I’ll keep writing here. Thanks for reading.

Pickle’s nine flaws

Saturday 20 June 2020

Python’s pickle module is a very convenient way to serialize and de-serialize objects. It needs no schema, and can handle arbitrary Python objects. But it has problems. This post briefly explains the problems.

Some people will tell you to never use pickle because it’s bad. I won’t go that far. I’ll say, only use pickle if you are OK with its nine flaws:

  • Insecure
  • Old pickles look like old code
  • Implicit
  • Over-serializes
  • __init__ isn’t called
  • Python only
  • Unreadable
  • Appears to pickle code
  • Slow

The flaws

Here is a brief explanation of each flaw, in roughly the order of importance.

Insecure

Pickles can be hand-crafted that will have malicious effects when you unpickle them. As a result, you should never unpickle data that you do not trust.

The insecurity is not because pickles contain code, but because they create objects by calling constructors named in the pickle. Any callable can be used in place of your class name to construct objects. Malicious pickles will use other Python callables as the “constructors.” For example, instead of executing “models.MyObject(17)”, a dangerous pickle might execute “os.system(‘rm -rf /’)”. The unpickler can’t tell the difference between “models.MyObject” and “os.system”. Both are names it can resolve, producing something it can call. The unpickler executes either of them as directed by the pickle.

More details, including an example, are in Supakeen’s post Dangers in Python’s standard library.

Old pickles look like old code

Because pickles store the structure of your objects, when they are unpickled, they have the same structure as when you pickled them. This sounds like a good thing and is exactly what pickle is designed to do. But if your code changes between the time you made the pickle and the time you used it, your objects may not correspond to your code. The objects will still have the structure created by the old code, but they will be running with the new code.

For example, if you’ve added an attribute since the pickle was made, the objects from the pickle won’t have that attribute. Your code will be expecting the attribute, causing problems.

Implicit

The great convenience of pickles is that they will serialize whatever structure your object has. There’s no extra work to create a serialization structure. But that brings problems of its own. Do you really want your datetimes serialized as datetimes? Or as iso8601 strings? You don’t have a choice: they will be datetimes.

Not only don’t you have to specify the serialization form, you can’t specify it.

Over-serializes

Pickles are implicit: they serialize everything in your objects, even data you didn’t want to serialize. For example, you might have an attribute that is a cache of computation that you don’t want serialized. Pickle doesn’t have a convenient way to skip that attribute.

Worse, if your object contains an attribute that can’t be pickled, like an open file object, pickle won’t skip it, it will insist on trying to pickle it, and then throw an exception.

__init__ isn’t called

Pickles store the entire structure of your objects. When the pickle module recreates your objects, it does not call your __init__ method, since the object has already been created.

This can be surprising, since nowhere else do objects come into being without calling __init__. The logic here is that __init__ was already called when the object was first created in the process that made the pickle.

But your __init__ method might perform some essential work, like opening file objects. Your unpickled objects will be in a state that is inconsistent with your __init__ method. Or your __init__ might log information about the object being created. Unpickled objects won’t appear in the log.

Python only

Pickles are specific to Python, and are only usable by other Python programs. This isn’t strictly true, you can find packages for other languages that can use pickles, but they are rare. They will naturally be limited to the cross-language generic list/dict object structures, at which point you might as well just use JSON.

Unreadable

A pickle is a binary data stream (actually instructions for an abstract execution engine.) If you open a pickle as a plain file, you cannot read its contents. The only way to know what is in a pickle is to use the pickle module to load it. This can make debugging difficult, since you might not be able to search your pickle files for data you are interested in:

>>> pickle.dumps([123, 456])
b'\x80\x03]q\x00(K{M\xc8\x01e.'

Appears to pickle code

Functions and classes are first-class objects in Python: you can store them in lists, dicts, attributes, and so on. Pickle will gladly serialize objects that contain callables like functions and classes. But it doesn’t store the code in the pickle, just the name of the function or class.

Pickles are not a way to move or store code, though they can appear to be. When you unpickle your data, the names of the functions are used to find existing code in your running process.

Slow

Compared to other serialization techniques, pickle can be slow as Ben Frederickson demonstrates in Don’t pickle your data.

But but..

Some of these problems can be addressed by adding special methods to your class, like __getstate__ or __reduce__. But once you start down that path, you might as well use another serialization method that doesn’t have these flaws to begin with.

What’s better?

There are lots of other ways to serialize objects, ranging from plain-old JSON to fancier alternatives like marshmallow, cattrs, protocol buffers, and more.

I don’t have a strong recommendation for any one of these. The right answer will depend on the particulars of your problem. It might even be pickle...

Black lives matter

Saturday 13 June 2020

Since Trump’s “election” in 2016, politics have been overwhelming enough that it’s been difficult to catch my breath long enough to write anything about it. These last few weeks have only intensified that feeling, but have also demanded a response of some sort.

George Floyd’s killing was egregious enough to finally light a match to tinder that had been drying and accumulating for a long time. As difficult as it is to confront the gross injustices that run through our society, it is encouraging to see people come together to call it out and address it.

I can try to speak up in my small way. It would be easy to sit back and say I have not personally seen problems with police or in how society treats me. But that is not evidence that all is well. The difference between my experience and others’ is precisely the problem.

People with privilege, the people who can do something, are the people who don’t experience the problems. We have to listen to others, to people not like us. We have to face difficult truths about our place in society. It doesn’t mean we are bad people. It doesn’t mean we have sought to subjugate others. Privilege doesn’t mean we don’t have our own challenges and struggles. But we benefit where others do not, and we have to act.

Trump’s incompetence, disregard, corruption, and malice are on full display now, because of both COVID-19 and the Black Lives Matter movement. There are signs that this could be a significant turning point. But it will not be easy, and conflicts will get worse before they get better.

I’m looking for ways to help. I can donate money, though the current extensive energy on the left means the progressive organization landscape is cluttered and confusing. I wish there was more I could do. I am looking for ideas.

Older: