
    
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
            
{"version":"https:\/\/jsonfeed.org\/version\/1","title":"mathspp.com feed","home_page_url":"https:\/\/mathspp.com\/blog\/til","feed_url":"https:\/\/mathspp.com\/blog\/til.json","description":"Stay up-to-date with the articles on mathematics and programming that get published to mathspp.com.","author":{"name":"Rodrigo Gir\u00e3o Serr\u00e3o"},"items":[{"title":"TIL #142 \u2013 Cyclic quadrilateral","date_published":"2026-03-14T14:58:00+01:00","id":"https:\/\/mathspp.com\/blog\/til\/cyclic-quadrilateral","url":"https:\/\/mathspp.com\/blog\/til\/cyclic-quadrilateral","content_html":"<p>Today I learned that cyclic quadrilaterals have supplementary opposite angles.<\/p>\n\n<p>A <strong>cyclic quadrilateral<\/strong> \u2014 a quadrilateral whose four vertices all lie on a single circle \u2014 has supplementary opposite angles.<\/p>\n<p>This means that opposite angles add to 180 degrees, or <span class=\"mathjax mathjax--inline\">\\(\\pi\\)<\/span> radians.<\/p>\n<p>As it turns out, this is actually an equivalence relation.\nIf a quadrilateral has supplementary opposite angles, it's a cyclic quadrilateral.<\/p>\n<p>This fact about supplementary opposite angles was very useful for an animation I was trying to create...\nI may share it here later!<\/p>","summary":"Today I learned that cyclic quadrilaterals have supplementary opposite angles.","date_modified":"2026-03-14T16:03:32+01:00","tags":["mathematics","geometry"],"image":"\/user\/pages\/02.blog\/04.til\/142.cyclic-quadrilateral\/thumbnail.webp"},{"title":"TIL #141 \u2013 Inspect a lazy import","date_published":"2026-03-13T14:38:00+01:00","id":"https:\/\/mathspp.com\/blog\/til\/inspect-a-lazy-import","url":"https:\/\/mathspp.com\/blog\/til\/inspect-a-lazy-import","content_html":"<p>Today I learned how to inspect a lazy import object in Python 3.15.<\/p>\n\n<p>Python 3.15 comes with lazy imports and today I played with them for a minute.\nI defined the following module <code>mod.py<\/code>:<\/p>\n<pre><code class=\"language-py\">print(\"Hey!\")\n\ndef f():\n    return \"Bye!\"<\/code><\/pre>\n<p>Then, in the REPL, I could check that lazy imports indeed work:<\/p>\n<pre><code class=\"language-pycon\">&gt;&gt;&gt; # Python 3.15\n&gt;&gt;&gt; lazy import mod\n&gt;&gt;&gt;<\/code><\/pre>\n<p>The fact that I didn't see a \"Hey!\" means that the import is, indeed, lazy.\nThen, I wanted to take a look at the module so I printed it, but that triggered reification (going from a lazy import to a regular module):<\/p>\n<pre><code class=\"language-pycon\">&gt;&gt;&gt; print(mod)\nHey!\n&lt;module 'mod' from '\/Users\/rodrigogs\/Documents\/tmp\/mod.py'&gt;<\/code><\/pre>\n<p>So, I checked <a href=\"https:\/\/peps.python.org\/pep-0810\/#reification\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">the PEP that introduced explicit lazy modules<\/a> and turns out as soon as you <em>reference<\/em> the lazy object directly, it gets reified.\nBut you can work around it by using <code>globals<\/code>:<\/p>\n<pre><code class=\"language-pycon\">&gt;&gt;&gt; # Fresh 3.15 REPL\n&gt;&gt;&gt; lazy import mod\n&gt;&gt;&gt; globals()[\"mod\"]\n&lt;lazy_import 'mod'&gt;<\/code><\/pre>\n<p>This shows the new class <code>lazy_import<\/code> that was added to support lazy imports!<\/p>\n<p>Pretty cool, right?<\/p>","summary":"Today I learned how to inspect a lazy import object in Python 3.15.","date_modified":"2026-03-13T15:54:14+01:00","tags":["programming","python"],"image":"\/user\/pages\/02.blog\/04.til\/141.inspect-a-lazy-import\/thumbnail.webp"},{"title":"TIL #140 \u2013 Install Jupyter with uv","date_published":"2026-03-03T16:16:00+01:00","id":"https:\/\/mathspp.com\/blog\/til\/install-jupyter-with-uv","url":"https:\/\/mathspp.com\/blog\/til\/install-jupyter-with-uv","content_html":"<p>Today I learned how to install jupyter properly while using uv to manage tools.<\/p>\n\n<h2 id=\"running-a-jupyter-notebook-server-or-jupyter-lab\">Running a Jupyter notebook server or Jupyter lab<a href=\"#running-a-jupyter-notebook-server-or-jupyter-lab\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>To run a Jupyter notebook server with uv, you can run the command<\/p>\n<pre><code class=\"language-bash\">$ uvx jupyter notebook<\/code><\/pre>\n<p>Similarly, if you want to run Jupyter lab, you can run<\/p>\n<pre><code class=\"language-bash\">$ uvx jupyter lab<\/code><\/pre>\n<p>Both work, but uv will kindly present a message explaining how it's actually doing you a favour, because it <em>guessed<\/em> what you wanted.\nThat's because <code>uvx something<\/code> usually looks for a package named \u201csomething\u201d with a command called \u201csomething\u201d.<\/p>\n<p>As it turns out, the command <code>jupyter<\/code> comes from the package <code>jupyter-core<\/code>, not from the package <code>jupyter<\/code>.<\/p>\n<h2 id=\"installing-jupyter\">Installing Jupyter<a href=\"#installing-jupyter\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>If you're running Jupyter notebooks often, you can install the notebook server and Jupyter lab with<\/p>\n<pre><code class=\"language-bash\">$ uv tool install --with jupyter jupyter-core<\/code><\/pre>\n<h3 id=\"why-uv-tool-install-jupyter-fails\">Why <code>uv tool install jupyter<\/code> fails<a href=\"#why-uv-tool-install-jupyter-fails\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p>Running <code>uv tool install jupyter<\/code> fails because the package <code>jupyter<\/code> doesn't provide any commands by itself.<\/p>\n<h3 id=\"why-uv-tool-install-jupyter-core-doesn-t-work\">Why <code>uv tool install jupyter-core<\/code> doesn't work<a href=\"#why-uv-tool-install-jupyter-core-doesn-t-work\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p>The command <code>uv tool install jupyter-core<\/code> looks like it works because it installs the command <code>jupyter<\/code> correctly.\nHowever, if you use <code>--help<\/code> you can see that you don't have access to the subcommands you need:<\/p>\n<pre><code class=\"language-bash\">$ uv tool install jupyter-core\n...\nInstalled 3 executables: jupyter, jupyter-migrate, jupyter-troubleshoot\n$ jupyter --help\n...\nAvailable subcommands: book migrate troubleshoot<\/code><\/pre>\n<p>That's because the subcommands <code>notebook<\/code> and <code>lab<\/code> are from the package <code>jupyter<\/code>.\nThe solution?\nInstall <code>jupyter-core<\/code> <em>with<\/em> the additional dependency <code>jupyter<\/code>, which is what the command <code>uv tool install --with jupyter jupyter-core<\/code> does.<\/p>\n<h2 id=\"other-usages-of-jupyter\">Other usages of Jupyter<a href=\"#other-usages-of-jupyter\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>The uv documentation has a <a href=\"https:\/\/docs.astral.sh\/uv\/guides\/integration\/jupyter\/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">page dedicated exclusively to the usage of uv with Jupyter<\/a>, so check it out for other use cases of the uv and Jupyter combo!<\/p>","summary":"Today I learned how to install jupyter properly while using uv to manage tools.","date_modified":"2026-03-03T18:05:58+01:00","tags":["python","programming","uv","productivity"],"image":"\/user\/pages\/02.blog\/04.til\/140.install-jupyter-with-uv\/thumbnail.webp"},{"title":"TIL #139 \u2013 Multiline input in the REPL","date_published":"2026-03-02T15:15:00+01:00","id":"https:\/\/mathspp.com\/blog\/til\/multiline-input-in-the-repl","url":"https:\/\/mathspp.com\/blog\/til\/multiline-input-in-the-repl","content_html":"<p>Today I learned how to do multiline input in the REPL using an uncommon combination of arguments for the built-in <code>open<\/code>.<\/p>\n\n<p>A while ago <a href=\"\/blog\/til\/020\">I learned I could use <code>open(0)<\/code> to open standard input<\/a>.\nThis unlocks a neat trick that allows you to do multiline input in the REPL:<\/p>\n<pre><code class=\"language-pycon\">&gt;&gt;&gt; msg = open(0).read()\nHello,\nworld!\n^D\n&gt;&gt;&gt; msg\n'Hello,\\nworld!\\n'<\/code><\/pre>\n<p>The cryptic <code>^D<\/code> is <kbd>Ctrl<\/kbd>+<kbd>D<\/kbd>, which means EOF on Unix systems.\nIf you're on Windows, use <kbd>Ctrl<\/kbd>+<kbd>Z<\/kbd>.<\/p>\n<p>The problem is that if you try to use <code>open(0).read()<\/code> again to read more multiline input, you get an exception:<\/p>\n<pre><code class=\"language-py\">OSError: [Errno 9] Bad file descriptor<\/code><\/pre>\n<p>That's because, when you finished reading the first time around, Python closed the file descriptor <code>0<\/code>, so you can no longer use it.<\/p>\n<p>The fix is to set <code>closefd=False<\/code> when you use the built-in <code>open<\/code>.\nWith the parameter <code>closefd<\/code> set to <code>False<\/code>, the underlying file descriptor isn't closed and you can reuse it:<\/p>\n<pre><code class=\"language-pycon\">&gt;&gt;&gt; msg1 = open(0, closefd=False).read()\nHello,\nworld!\n^D\n&gt;&gt;&gt; msg1\n'Hello,\\nworld!\\n'\n\n&gt;&gt;&gt; msg2 = open(0, closefd=False).read()\nGoodbye,\nworld!\n^D\n&gt;&gt;&gt; msg2\n'Goodbye,\\nworld!\\n'<\/code><\/pre>\n<p>By using <code>open(0, closefd=False)<\/code>, you can read multiline input in the REPL <em>repeatedly<\/em>.<\/p>","summary":"Today I learned how to do multiline input in the REPL using an uncommon combination of arguments for the built-in open.","date_modified":"2026-03-02T16:21:46+01:00","tags":["repl","programming","python"],"image":"\/user\/pages\/02.blog\/04.til\/139.multiline-input-in-the-repl\/thumbnail.webp"},{"title":"TIL #138 \u2013 Custom directives in Jupyter Book","date_published":"2026-01-02T22:22:00+01:00","id":"https:\/\/mathspp.com\/blog\/til\/custom-directives-in-jupyter-book","url":"https:\/\/mathspp.com\/blog\/til\/custom-directives-in-jupyter-book","content_html":"<p>Today I learned how to create and register a simple Sphinx extension to use as a custom directive in a Jupyter Book project.<\/p>\n\n<p>I wanted to create a custom directive that I could use in a <a href=\"https:\/\/jupyterbook.org\/v1\/start\/overview.html\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">Jupyter Book<\/a> project that would look like this:<\/p>\n<pre><code>Some prose goes here.\n\n```{mypy} snippet.py\n```<\/code><\/pre>\n<p>Then, the directive <code>{mypy}<\/code> would run mypy against the file <code>snippet.py<\/code> and include the mypy output in the book.<\/p>\n<p>With the help of ChatGPT I was able to quickly whip up a Sphinx extension that defines this directive, including the ability to infer the location of my Python snippets based on the concrete structure I have for this project I'm working on: a file <code>snippet.py<\/code> mentioned in a chapter called <code>xx.my-chapter.md<\/code> can be found in <code>snippets\/my-chapter\/snippet.py<\/code>, where <code>snippets<\/code> is at the root of the project.<\/p>\n<p>After a bit of back and forth and some manual tweaks, this is the directive I ended up with:<\/p>\n<details><summary><code>_ext\/mypy_directive.py<\/code><\/summary><pre><code>\"\"\"\nCreates a Sphinx directive {mypy} that runs mypy on the given file and includes the output.\nWhen used as ```{mypy} script.py in a file called `xx.some-chapter.md`, this directive\nwill try to find the file in `snippets\/some-chapter\/script.py`.\nIf the file argument contains slashes, it is interpreted as an absolute path.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport subprocess\nfrom pathlib import Path\nfrom typing import Any, List\n\nfrom docutils import nodes\nfrom sphinx.util.docutils import SphinxDirective\n\nclass MypyDirective(SphinxDirective):\n    \"\"\"\n    Usage (MyST):\n        ```{mypy} path\/to\/file.py\n        :flags: --strict --show-error-codes\n        ```\n    \"\"\"\n\n    required_arguments = 1  # the file path\n    optional_arguments = 0\n    has_content = False\n\n    option_spec = {\n        \"flags\": lambda s: s,  # pass extra mypy CLI flags as a single string\n    }\n\n    def run(self) -&amp;gt; List[nodes.Node]:\n        script_arg = self.arguments[0]\n        env = self.env\n\n        # Get current document filename (e.g. \"given-chapter\")\n        # env.docname is like \"chapters\/given-chapter\" (no extension)\n        _, _, chapter_name = Path(env.docname).name.partition(\".\")\n\n        # If the user passed just \"script.py\", infer snippets\/&lt;chapter&gt;\/&lt;script.py&gt;.\n        # If they passed a path with a slash, treat it as explicit.\n        if \"\/\" not in script_arg and \"\\\\\" not in script_arg:\n            inferred_rel = str(Path(\"snippets\") \/ chapter_name \/ script_arg)\n        else:\n            inferred_rel = script_arg\n\n        # Sphinx helper: resolve filenames relative to doc, and track dependencies\n        _, abs_path_str = env.relfn2path(inferred_rel)\n        abs_path = Path(abs_path_str)\n\n        # Ensure rebuilds happen when the file changes\n        env.note_dependency(str(abs_path))\n\n        if not abs_path.exists():\n            msg = f\"[mypy] File not found: {abs_path}\"\n            return [nodes.literal_block(text=msg)]\n\n        flags = self.options.get(\"flags\", \"\").strip()\n        cmd: list[str] = [\"mypy\", str(abs_path)]\n        if flags:\n            cmd.extend(flags.split())\n\n        proc = subprocess.run(\n            cmd,\n            stdout=subprocess.PIPE,\n            stderr=subprocess.STDOUT,\n            text=True,\n            cwd=abs_path.parent,\n        )\n\n        output = proc.stdout.rstrip()\n        if not output:\n            output = \"[mypy] (no output)\"\n\n        output = f\"$ mypy {abs_path.name}\\n\" + output\n\n        # Render as a literal block (monospace). &ldquo;language&rdquo; here is just for CSS\/classes.\n        block = nodes.literal_block(output, output)\n        block[\"language\"] = \"text\"\n        return [block]\n\ndef setup(app: Any) -&amp;gt; dict[str, Any]:\n    app.add_directive(\"mypy\", MypyDirective)\n    return {\"version\": \"0.1\", \"parallel_read_safe\": True, \"parallel_write_safe\": True}<\/code><\/pre>\n<p>&lt;\/script.py&gt;<\/p>\n<\/details><p>To be able to use it, I had to tweak the book configuration to tell it where to find my extension:<\/p>\n<pre><code class=\"language-yaml\">sphinx:\n  ...\n  local_extensions:\n    mypy_directive: _ext\n  extra_extensions:\n    - mypy_directive<\/code><\/pre>\n<p>This assumes the code <code>mypy_directive.py<\/code> lives inside <code>_ext<\/code> in...<\/p>","summary":"Today I learned how to create and register a simple Sphinx extension to use as a custom directive in a Jupyter Book project.","date_modified":"2026-01-02T23:39:07+01:00","tags":["productivity","llm"],"image":"\/user\/pages\/02.blog\/04.til\/138.custom-directives-in-jupyter-book\/thumbnail.webp"},{"title":"TIL #137 \u2013 Inline SVGs in Jupyter notebooks","date_published":"2025-11-23T23:34:00+01:00","id":"https:\/\/mathspp.com\/blog\/til\/inline-svgs-in-jupyter-notebooks","url":"https:\/\/mathspp.com\/blog\/til\/inline-svgs-in-jupyter-notebooks","content_html":"<p>Today I learned how to inline SVGs in Jupyter notebooks in two simple steps.<\/p>\n\n<p>Today I learned how to inline SVGs in Jupyter notebooks in two simple steps:<\/p>\n<ol>\n<li>URL-encode the SVG markup. If you have an SVG file, open it and copy the contents of the file starting from the <code>&lt;svg&gt;<\/code> tag all the way up to the closing <code>&lt;\/svg&gt;<\/code> and encode it so it's safe to use in a URL. You can use <a href=\"https:\/\/tools.mathspp.com\/url-encode\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">this URL encoder tool I created<\/a>.<\/li>\n<li>Add an image using Markdown syntax with <code>![ALT text](data:image\/svg+xml,&lt;URL-encoded string here&gt;)<\/code>.<\/li>\n<\/ol>\n<p>For any non-trivial SVG the URL-encoded string will look huge and nasty, as the image below shows:<\/p>\n<figure class=\"image-caption\"><img title=\"The URL-encoded SVG.\" alt=\"Screenshot of a Jupyter notebok with a Markdown cell being edited. The markup in the cell starts with \u201c![](data:image\/svg+xml,\u201d and is followed by a very long string of weird-looking characters with lots of percent signs.\" src=\"\/user\/pages\/02.blog\/04.til\/137.inline-svgs-in-jupyter-notebooks\/_markup.webp\"><figcaption class=\"\">The URL-encoded SVG.<\/figcaption><\/figure>\n<p>But when I \u201cexecute\u201d the cell to render the Markdown, the SVG displays neatly:<\/p>\n<figure class=\"image-caption\"><img title=\"The rendered SVG.\" alt=\"Screenshot of an SVG showing 8 diagrams with white and black squares arranged in the same shape but with different colouring schemes.\" src=\"\/user\/pages\/02.blog\/04.til\/137.inline-svgs-in-jupyter-notebooks\/_svg.webp\"><figcaption class=\"\">The rendered SVG.<\/figcaption><\/figure>\n<p>This was an interesting endeavour because I thought I could just paste the SVG markup in the notebook cell and it would be rendered; I was under the impression that you could write arbitrary HTML in those cells.\nI was either wrong or I did it in the wrong way!<\/p>","summary":"Today I learned how to inline SVGs in Jupyter notebooks in two simple steps.","date_modified":"2025-11-24T01:18:24+01:00","tags":["productivity","programming"],"image":"\/user\/pages\/02.blog\/04.til\/137.inline-svgs-in-jupyter-notebooks\/thumbnail.webp"},{"title":"TIL #136 \u2013 Publish an EPUB book with Jupyter Book","date_published":"2025-11-17T11:01:00+01:00","id":"https:\/\/mathspp.com\/blog\/til\/publish-an-epub-book-with-jupyter-book","url":"https:\/\/mathspp.com\/blog\/til\/publish-an-epub-book-with-jupyter-book","content_html":"<p>Today I learned how to set the configurations of my Jupyter Book to build my book in the EPUB format.<\/p>\n\n<h1 id=\"publish-an-epub-book-with-jupyter-book\">Publish an EPUB book with Jupyter Book<a href=\"#publish-an-epub-book-with-jupyter-book\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h1>\n<p>I've been redoing my book <a href=\"\/books\/pydonts\">Pydon'ts \u2013 Write elegant Python code<\/a> in <a href=\"https:\/\/jupyterbook.org\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">Jupyter Book<\/a> (v1) because the PDFs have great typesetting defaults and look much better than what I currently have with pandoc plus a couple of custom filters.<\/p>\n<p>I was trying to also get Jupyter Book to build my book in EPUB format but was struggling a bit with it because I was getting a couple of weird warnings when I ran the build command:<\/p>\n<pre><code class=\"language-bash\">bash % jb build --all --builder=custom --custom-builder=epub .<\/code><\/pre>\n<p>This created an EPUB with the name <code>Projectnamenotset.epub<\/code> and the book title was <code>Projectnamenotset<\/code>, so I knew something was off.<\/p>\n<p>The configuration file had the correct metadata:<\/p>\n<pre><code class=\"language-yaml\"># _config.yml\ntitle: Pydon'ts \u2013 Write elegant Python code\nauthor: Rodrigo Gir\u00e3o Serr\u00e3o\n# ...<\/code><\/pre>\n<div class=\"notices yellow\">\n<p>Strictly speaking, I was only getting warnings so the build process was working fine.\nBut I wanted to run the flag <code>-W<\/code>, which turns warnings into errors, so I was hitting a couple of roadblocks on top of the weird project name.<\/p>\n<\/div>\n<h2 id=\"epub3-requires-a-version\">EPUB3 requires a version<a href=\"#epub3-requires-a-version\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>I was getting a warning saying that the format EPUB3 required a non-empty version, which just meant I had to specify a version in the Sphinx config:<\/p>\n<pre><code class=\"language-yaml\">sphinx:\n  config:\n    version: \"2025.11.17\"<\/code><\/pre>\n<h2 id=\"setting-the-epub-file-name\">Setting the EPUB file name<a href=\"#setting-the-epub-file-name\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>To set the EPUB file name to something other than <code>Projectnamenotset.epub<\/code> I had to set the option <code>epub_basename<\/code> in the Sphinx config:<\/p>\n<pre><code class=\"language-yaml\"># _config.yml\nsphinx:\n  config:\n    # ...\n    epub_basename: \"pydonts\"<\/code><\/pre>\n<h2 id=\"setting-the-epub-title\">Setting the EPUB title<a href=\"#setting-the-epub-title\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>Although I had the <code>title<\/code> metadata set, I had to set it again in the Sphinx config so it would show as the EPUB title:<\/p>\n<pre><code class=\"language-yaml\">sphinx:\n  config:\n    # ...\n    epub_title: \"Pydon'ts \u2013 Write elegant Python code\"<\/code><\/pre>\n<h2 id=\"unknown-mimetype-for-index-html\">Unknown mimetype for <code>index.html<\/code><a href=\"#unknown-mimetype-for-index-html\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>Since my root file is not called <code>index<\/code>, I was also getting a warning saying <code>sphinx.errors.SphinxWarning: unknown mimetype for index.html<\/code>.<\/p>\n<p>Long story short, an extension wants me to have the file <code>index.html<\/code> so that I can always navigate to the root URL and see something, and then the file <code>index.html<\/code> just redirects to my custom root, which is <code>foreword<\/code> in this case:<\/p>\n<pre><code class=\"language-yaml\"># _toc.yml\nformat: jb-book\nroot: foreword  # &lt;--\nparts:\n  - caption: Introduction\n    chapters:\n    - file: pydonts\/pydont-disrespect-the-zen-of-python.md\n    # ...<\/code><\/pre>\n<p>So, I had to tell the EPUB builder to ignore the file <code>index.html<\/code>, that was being built but shouldn't be used when building the final EPUB:<\/p>\n<pre><code class=\"language-yaml\">sphinx:\n  config:\n    epub_exclude_files:\n      - \"index.html\"<\/code><\/pre>\n<h2 id=\"final-configuration\">Final configuration<a href=\"#final-configuration\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>Here's the final set of configurations I use to build the EPUB:<\/p>\n<pre><code class=\"language-yaml\">sphinx:\n  config:\n    epub_exclude_files:\n      - \"index.html\"\n    epub_basename: \"pydonts\"\n    epub_title: \"Pydon'ts \u2013 Write elegant Python code\"\n    version: \"2025.11.17\"\n    language: en<\/code><\/pre>\n<p>I build with this command:<\/p>\n<pre><code class=\"language-bash\">bash % jb build --all --builder=custom --custom-builder=epub -W .<\/code><\/pre>","summary":"Today I learned how to set the configurations of my Jupyter Book to build my book in the EPUB format.","date_modified":"2025-11-17T12:20:19+01:00","tags":["productivity"],"image":"\/user\/pages\/02.blog\/04.til\/136.publish-an-epub-book-with-jupyter-book\/thumbnail.webp"},{"title":"TIL #135 \u2013 Build the Python documentation","date_published":"2025-10-26T14:44:00+01:00","id":"https:\/\/mathspp.com\/blog\/til\/build-the-python-documentation","url":"https:\/\/mathspp.com\/blog\/til\/build-the-python-documentation","content_html":"<p>Today I learned how to build the Python documentation to preview changes I wanted to make.<\/p>\n\n<p>If you're not on Windows, all it takes is to run <code>make -C Doc venv htmllive<\/code> to build the Python documentation locally and to preview it.\nThis command will build the documentation, start a local server to browse the docs, and also watch for changes in the documentation source files to live-reload while you edit!<\/p>\n<p>I needed this because the Python 3.14 documentation for the module <code>concurrent.interpreters<\/code> had a terrible-looking \u201cSee also\u201d callout with elements that were grossly misaligned:<\/p>\n<figure class=\"image-caption\"><img title=\"This makes my want to cry.\" alt=\"\u201cSee also\u201d callout with elements that are grossly misaligned\" src=\"\/user\/pages\/02.blog\/04.til\/135.build-the-python-documentation\/_bad.webp\"><figcaption class=\"\">This makes my want to cry.<\/figcaption><\/figure>\n<p>However, since I don't know rST, only Markdown, the issue wasn't obvious to me:<\/p>\n<pre><code class=\"language-rst\">.. seealso::\n\n   :class:`~concurrent.futures.InterpreterPoolExecutor`\n      combines threads with interpreters in a familiar interface.\n\n    .. XXX Add references to the upcoming HOWTO docs in the seealso block.\n\n   :ref:`isolating-extensions-howto`\n       how to update an extension module to support multiple interpreters\n\n   :pep:`554`\n\n   :pep:`734`\n\n   :pep:`684`<\/code><\/pre>\n<p>After some Googling, turns out the problem is the comment <code>.. XXX Add references...<\/code>.\nSince it's indentend four spaces, it's being interpreted as a blockquote!\nThe fix was just deleting a single space from the left of <code>.. XXX ...<\/code>.<\/p>\n<p>However, I did not stop there!\nI went above and beyond, capitalising the sentences and adding a full stop to the one that didn't have it!<\/p>\n<p>In the end, the \u201cSee also\u201d callout was looking better:<\/p>\n<figure class=\"image-caption\"><img title=\"What a work of art.\" alt=\"\u201cSee also\u201d callout with elements that are neatly aligned and with better-looking punctuation and capitalisation.\" src=\"\/user\/pages\/02.blog\/04.til\/135.build-the-python-documentation\/_good.webp\"><figcaption class=\"\">What a work of art.<\/figcaption><\/figure>","summary":"Today I learned how to build the Python documentation to preview changes I wanted to make.","date_modified":"2025-10-26T15:51:58+01:00","tags":["python","open source"],"image":"\/user\/pages\/02.blog\/04.til\/135.build-the-python-documentation\/thumbnail.webp"},{"title":"TIL #134 \u2013 = alignment in string formatting","date_published":"2025-10-04T16:01:00+02:00","id":"https:\/\/mathspp.com\/blog\/til\/-alignment-in-string-formatting","url":"https:\/\/mathspp.com\/blog\/til\/-alignment-in-string-formatting","content_html":"<p>Today I learned how to use the equals sign to align numbers when doing string formatting in Python.<\/p>\n\n<p>There are three main alignment options in Python's string formatting:<\/p>\n<table>\n<thead>\n<tr>\n<th>Character<\/th>\n<th>Meaning<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td><code>&lt;<\/code><\/td>\n<td>align left<\/td>\n<\/tr>\n<tr>\n<td><code>&gt;<\/code><\/td>\n<td>align right<\/td>\n<\/tr>\n<tr>\n<td><code>^<\/code><\/td>\n<td>centre<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>However, numbers have a fourth option <code>=<\/code>.\nOn the surface, it looks like it doesn't do anything:<\/p>\n<pre><code class=\"language-py\">x = 73\n\nprint(f\"@{x:10}@\")   # @        73@\nprint(f\"@{x:=10}@\")  # @        73@<\/code><\/pre>\n<p>But that's because <code>=<\/code> influences the alignment of the sign.\nIf I make <code>x<\/code> negative, we already see something:<\/p>\n<pre><code class=\"language-py\">x = -73\n\nprint(f\"@{x:10}@\")   # @       -73@\nprint(f\"@{x:=10}@\")  # @-       73@<\/code><\/pre>\n<p>So, the equals sign <code>=<\/code> aligns a number to the right but aligns its sign to the left.\nThat may look weird, but I guess that's useful if you want to pad a number with 0s:<\/p>\n<pre><code class=\"language-py\">x = -73\n\nprint(f\"@{x:010}@\")  # @-000000073@<\/code><\/pre>\n<p>In fact, there is a shortcut for this type of alignment, which is to just put a zero immediately to the left of the width when aligning a number:<\/p>\n<pre><code class=\"language-py\">x = -73\n\nprint(f\"@{x:010}@\")  # @-000000073@<\/code><\/pre>\n<p>The zero immediately to the left changes the default alignment of numbers to be <code>=<\/code> instead of <code>&gt;<\/code>.<\/p>","summary":"Today I learned how to use the equals sign to align numbers when doing string formatting in Python.","date_modified":"2025-10-20T22:34:56+02:00","tags":["python","programming"],"image":"\/user\/pages\/02.blog\/04.til\/134.-alignment-in-string-formatting\/thumbnail.webp"},{"title":"TIL #133 \u2013 Shoelace formula","date_published":"2025-09-25T12:16:00+02:00","id":"https:\/\/mathspp.com\/blog\/til\/shoelace-formula","url":"https:\/\/mathspp.com\/blog\/til\/shoelace-formula","content_html":"<p>Today I learned about the shoelace formula to compute the area of arbitrary simple polygons.<\/p>\n\n<p>If you have a polygon with no holes and that doesn't intersect itself you can use the shoelace formula to compute its area. If <span class=\"mathjax mathjax--inline\">\\(P_i = (x_i, y_i), i = 1, \\cdots, n\\)<\/span> are the vertices of the polygon, then the area is given by<\/p>\n<p class=\"mathjax mathjax--block\">\\[\nA = \\frac12 \\left| \\sum_{i = 1}^{n} x_iy_{i + 1} - y_i x_{i + 1} \\right|\\]<\/p>\n<p>In the formula above, <span class=\"mathjax mathjax--inline\">\\(P_{n + 1}\\)<\/span> is <span class=\"mathjax mathjax--inline\">\\(P_1\\)<\/span>.<\/p>\n<p>The formula is super practical and easy to compute, which I find amusing given that it works for any (simple) polygon!<\/p>","summary":"Today I learned about the shoelace formula to compute the area of arbitrary simple polygons.","date_modified":"2025-10-20T22:34:56+02:00","tags":["mathematics","geometry"],"image":"\/user\/pages\/02.blog\/04.til\/133.shoelace-formula\/thumbnail.webp"}]}
