Python REPL as a presentation framework

I needed a presentation software for a talk at a Python meetup (in Karlsruhe, but I’ve since held the talk at the Python meetup Stuttgart, too). I wanted something simple with which I could show code easily, but ideally there would be some element of interactivity, too (what if I could demonstrate the code I show within the presentation itself?).

One solution would of course be to fake it: I could just pretend something is running and show the output I would see, maybe after some delay. This is something even PowerPoint can do. But this is no fun, I wanted real Python execution in the background. Next idea: Build my own script that can somehow take Python code and eval/exec it inbetween showing slides. Maybe something browser-based? Or I could make a fake Python REPL and interpret some special strings as commands for showing “slides” in a terminal.

Then I saw a talk by the mad scientist of the Python universe where he appeared to kind of do what I wanted, with what he called a “modded Python interpreter”. That gave me an idea: What if I didn’t have to write a fake REPL, but could use the real one?

Objects in Python can determine how their representation looks like, so I could define a custom object that remembers the slide number and prints different text each time it is repr()d. If I want to have a “next” and “previous” operation, I could have two special objects that share the state of which slide I’m currently on.

__repr__ methods with side-effects was the first idea, but then I found another, easier, hackier solution: sys.displayhook. It’s a function that gets called by the Python REPL whenever an object is being printed interactively, its job is this printing. If I changed that, I wouldn’t even need special objects (and in the final version I don’t; the two variables n and p are just assigned to the strings "n" and "p" and my custom displayhook does the rest).

Next challenge: I wanted to do what’s known as a “prover” in the world of magicians: Some way of (falsely) convincing the audience that there is no trickery going on. Ideally, I would want to start a Python interpreter with the python3 command and have the presentation framework function without having to import things or running any extra code. While I could have used a shell alias or something like that to do this, that would have meant I would have had to recreate the Python REPL header, since python -i some_module.py doesn’t print it. Instead, I went for a different tool: Environment variables. Python looks at a few at startup, one of them being PYTHONSTARTUP, which I just had to set to my script that replaces the displayhook and I’m done. I can do that before the presentation or even put it in my .zshrc, so nobody needs to know.

I had most of the program set up, but I was still missing the actual presentation (who hasn’t written a presentation framework in order to procrastinate preparing a talk?). I wanted it to be easily editable, and I wanted the ability to show code and have it executed in the background.

As usual, I chose Markdown, as it’s easy to edit, expressive enough to make simple presentations, and text-based. A combination of mistune and colorful allowed me to write a custom Markdown renderer that outputs ANSI escape sequences to do color and font weight. Using pygments I could syntax-highlight code. Markdown supports specifying a language in the beginning of a fenced code block, so I invented some fake languages like exec, and exechidden that would not only highlight like Python, but actually execute the code and allow the resulting names to be used:

def block_code(self, text, lang=None):
    if lang and lang.startswith("exec"):
        bindings = {}
        exec(text, bindings)
        for key, value in bindings.items():
            if key == "__builtins__":
                continue
            setattr(builtins, key, value)

The only Markdown feature I found hard to do in a terminal were headings. Yes, I could have just printed them as in Markdown, with an octothorpe in front, or made them all-caps. I wanted different font sizes.

Enter Pillow, your friendly neighbourhood PIL clone. Whenever I encounter a heading, I draw text on a PIL.Image that has the size of my terminal window. I then convert the 256-color-grayscale image to an 8-color-grayscale image and then use the shaded block unicode characters (░▒▓█) to print the result.

Finally, I wanted multi-language support. I prepared the talk in English, but I wanted to give the audience an option to switch to German instead. To do this in an interesting way, I reused the sys.displayhook trick from earlier and introduced some additional piece of state: the mode. Usually, the mode is set to "normal", but when I type l, which contains "l", the shortcut for language selection, it switches to a special mode that will cause the display hook to intercept the next input and set a configuration value based on a selection from a menu:

>>> l
[0] german
[1] english
[2] romanian
>>> 2
Switching language to romanian
>>>

You can find the whole thing at https://github.com/L3viathan/triotalk, including the actual talk.


But there’s one more thing. If someone had figured this whole thing out during the talk and called me out on it, I wanted to have an extra ace up my sleeve.

I found the answer in sys.excepthook, which is similar to sys.displayhook, but is responsible for printing exceptions and their stacktraces. This allowed me to do this (17 is not really the return value, it is merely printed):

>>> 4/0
17

L3viathan

Recreational linguistics, distributional gastronomics, and applied galettalogy.