
    
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
            
{"version":"https:\/\/jsonfeed.org\/version\/1","title":"mathspp.com feed","home_page_url":"https:\/\/mathspp.com\/blog\/tags\/programming","feed_url":"https:\/\/mathspp.com\/blog\/tags\/programming.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":"itertools cheatsheet","date_published":"2026-07-13T16:28:00+02:00","id":"https:\/\/mathspp.com\/blog\/itertools-cheatsheet","url":"https:\/\/mathspp.com\/blog\/itertools-cheatsheet","content_html":"<p>Cheatsheet with visual diagrams that explain how the iterables from <code>itertools<\/code> work.<\/p>\n\n<p>This cheatsheet contains diagrams that explain how the iterables from the module <code>itertools<\/code> work in a visual way.<\/p>\n<p><a href=\"https:\/\/gumroad.com\/l\/cheatsheet-itertools\" class=\"btn btn-lg btn-center external-link no-image\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">Download this cheatsheet<\/a><\/p>\n<p><img alt=\"A4 itertools cheatsheet shown in light and dark themes.\" class=\"dark-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_cheatsheets-light-front.webp\"><\/p>\n<p><img alt=\"A4 itertools cheatsheet shown in light and dark themes.\" class=\"light-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_cheatsheets-dark-front.webp\"><\/p>\n<p><a href=\"https:\/\/gumroad.com\/l\/itertools-uv\" class=\"btn btn-lg btn-center external-link no-image\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">Download this cheatsheet<\/a><\/p>\n<hr>\n<h2 id=\"reshaping\">Reshaping<a href=\"#reshaping\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<h3 id=\"batched\"><code>batched<\/code><a href=\"#batched\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p><img alt=\"Visual diagram explaining how batched works.\" class=\"dark-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_batched_dark_bg.webp\">\n<img alt=\"Visual diagram explaining how batched works.\" class=\"light-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_batched_light_bg.webp\"><\/p>\n<h3 id=\"islice\"><code>islice<\/code><a href=\"#islice\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p><img alt=\"Visual diagram explaining how islice works.\" class=\"dark-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_islice_dark_bg.webp\">\n<img alt=\"Visual diagram explaining how islice works.\" class=\"light-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_islice_light_bg.webp\"><\/p>\n<h3 id=\"pairwise\"><code>pairwise<\/code><a href=\"#pairwise\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p><img alt=\"Visual diagram explaining how pairwise works.\" class=\"dark-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_pairwise_dark_bg.webp\">\n<img alt=\"Visual diagram explaining how pairwise works.\" class=\"light-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_pairwise_light_bg.webp\"><\/p>\n<h3 id=\"chain\"><code>chain<\/code><a href=\"#chain\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p><img alt=\"Visual diagram explaining how chain works.\" class=\"dark-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_chain_dark_bg.webp\">\n<img alt=\"Visual diagram explaining how chain works.\" class=\"light-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_chain_light_bg.webp\"><\/p>\n<h3 id=\"groupby\"><code>groupby<\/code><a href=\"#groupby\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p><img alt=\"Visual diagram explaining how groupby works.\" class=\"dark-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_groupby_dark_bg.webp\">\n<img alt=\"Visual diagram explaining how groupby works.\" class=\"light-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_groupby_light_bg.webp\"><\/p>\n<h2 id=\"filtering\">Filtering<a href=\"#filtering\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<h3 id=\"filterfalse\"><code>filterfalse<\/code><a href=\"#filterfalse\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p><img alt=\"Visual diagram explaining how filterfalse works.\" class=\"dark-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_filterfalse_dark_bg.webp\">\n<img alt=\"Visual diagram explaining how filterfalse works.\" class=\"light-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_filterfalse_light_bg.webp\"><\/p>\n<h3 id=\"takewhile-dropwhile\">\n<code>takewhile<\/code> &amp; <code>dropwhile<\/code>\n<a href=\"#takewhile-dropwhile\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p><img alt=\"Visual diagram explaining how takewhile and dropwhile work.\" class=\"dark-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_takewhile_dropwhile_dark_bg.webp\">\n<img alt=\"Visual diagram explaining how takewhile and dropwhile work.\" class=\"light-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_takewhile_dropwhile_light_bg.webp\"><\/p>\n<h3 id=\"compress\"><code>compress<\/code><a href=\"#compress\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p><img alt=\"Visual diagram explaining how compress works.\" class=\"dark-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_compress_dark_bg.webp\">\n<img alt=\"Visual diagram explaining how compress works.\" class=\"light-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_compress_light_bg.webp\"><\/p>\n<h2 id=\"infinite\">Infinite<a href=\"#infinite\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<h3 id=\"repeat\"><code>repeat<\/code><a href=\"#repeat\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p><img alt=\"Visual diagram explaining how repeat works.\" class=\"dark-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_repeat_dark_bg.webp\">\n<img alt=\"Visual diagram explaining how repeat works.\" class=\"light-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_repeat_light_bg.webp\"><\/p>\n<h3 id=\"cycle\"><code>cycle<\/code><a href=\"#cycle\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p><img alt=\"Visual diagram explaining how cycle works.\" class=\"dark-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_cycle_dark_bg.webp\">\n<img alt=\"Visual diagram explaining how cycle works.\" class=\"light-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_cycle_light_bg.webp\"><\/p>\n<h3 id=\"count\"><code>count<\/code><a href=\"#count\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p><img alt=\"Visual diagram explaining how count works.\" class=\"dark-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_count_dark_bg.webp\">\n<img alt=\"Visual diagram explaining how count works.\" class=\"light-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_count_light_bg.webp\"><\/p>\n<h2 id=\"complementary\">Complementary<a href=\"#complementary\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<h3 id=\"zip-longest\"><code>zip_longest<\/code><a href=\"#zip-longest\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p><img alt=\"Visual diagram explaining how zip_longest works.\" class=\"dark-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_zip_longest_dark_bg.webp\">\n<img alt=\"Visual diagram explaining how zip_longest works.\" class=\"light-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_zip_longest_light_bg.webp\"><\/p>\n<h3 id=\"starmap\"><code>starmap<\/code><a href=\"#starmap\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p><img alt=\"Visual diagram explaining how starmap works.\" class=\"dark-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_starmap_dark_bg.webp\">\n<img alt=\"Visual diagram explaining how starmap works.\" class=\"light-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_starmap_light_bg.webp\"><\/p>\n<h3 id=\"accumulate\"><code>accumulate<\/code><a href=\"#accumulate\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p><img alt=\"Visual diagram explaining how accumulate works.\" class=\"dark-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_accumulate_dark_bg.webp\">\n<img alt=\"Visual diagram explaining how accumulate works.\" class=\"light-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_accumulate_light_bg.webp\"><\/p>\n<p>By default, <code>accumulate<\/code> uses addition as its operation.\nThe magic in using <code>functools.reduce<\/code> and <code>itertools.accumulate<\/code> well is in realising the function that combines elements can be an arbitrary function of two arguments.\nFor example, you can imagine you're a painter and you're going to mix the colours in your palette:<\/p>\n<p><img alt=\"Visual diagram explaining how accumulate works.\" class=\"dark-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_accumulate_mix_dark_bg.webp\">\n<img alt=\"Visual diagram explaining how accumulate works.\" class=\"light-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_accumulate_mix_light_bg.webp\"><\/p>\n<h2 id=\"combinatorial\">Combinatorial<a href=\"#combinatorial\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<h3 id=\"product\"><code>product<\/code><a href=\"#product\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p><img alt=\"Visual diagram explaining how product works.\" class=\"dark-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_product_dark_bg.webp\">\n<img alt=\"Visual diagram explaining how product works.\" class=\"light-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_product_light_bg.webp\"><\/p>\n<p>When you want to compute the product of an iterable with itself two or more times, you can also use the argument <code>repeat<\/code>:<\/p>\n<p><img alt=\"Visual diagram explaining how product works.\" class=\"dark-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_product_repeat_dark_bg.webp\">\n<img alt=\"Visual diagram explaining how product works.\" class=\"light-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_product_repeat_light_bg.webp\"><\/p>\n<h3 id=\"permutations\"><code>permutations<\/code><a href=\"#permutations\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p><img alt=\"Visual diagram explaining how permutations work.\" class=\"dark-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_permutations_dark_bg.webp\">\n<img alt=\"Visual diagram explaining how permutations work.\" class=\"light-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_permutations_light_bg.webp\"><\/p>\n<h3 id=\"combinations\"><code>combinations<\/code><a href=\"#combinations\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p><img alt=\"Visual diagram explaining how combinations work.\" class=\"dark-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_combinations_dark_bg.webp\">\n<img alt=\"Visual diagram explaining how combinations work.\" class=\"light-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_combinations_light_bg.webp\"><\/p>\n<h3 id=\"combinations-with-replacement\"><code>combinations_with_replacement<\/code><a href=\"#combinations-with-replacement\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p><img alt=\"Visual diagram explaining how combinations_with_replacement works.\" class=\"dark-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_combinations_with_replacement_dark_bg.webp\">\n<img alt=\"Visual diagram explaining how combinations_with_replacement works.\" class=\"light-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_combinations_with_replacement_light_bg.webp\"><\/p>\n<hr>\n<p><a href=\"https:\/\/gumroad.com\/l\/cheatsheet-itertools\" class=\"btn btn-lg btn-center external-link no-image\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">Download this cheatsheet<\/a><\/p>\n<p><img alt=\"A4 itertools cheatsheet shown in light and dark themes.\" class=\"dark-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_cheatsheets-light-front.webp\"><\/p>\n<p><img alt=\"A4 itertools cheatsheet shown in light and dark themes.\" class=\"light-theme-only\" src=\"\/user\/pages\/02.blog\/itertools-cheatsheet\/_cheatsheets-dark-front.webp\"><\/p>\n<p><a href=\"https:\/\/gumroad.com\/l\/itertools-uv\" class=\"btn btn-lg btn-center external-link no-image\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">Download this cheatsheet<\/a><\/p>","summary":"Cheatsheet with visual diagrams that explain how the iterables from itertools work.","date_modified":"2026-07-22T18:06:55+02:00","tags":["python","programming","itertools","modules"],"image":"\/user\/pages\/02.blog\/itertools-cheatsheet\/thumbnail.webp"},{"title":"Write a coding agent from first principles: better tools","date_published":"2026-07-06T15:00:00+02:00","id":"https:\/\/mathspp.com\/blog\/write-a-coding-agent-from-first-principles-better-tools","url":"https:\/\/mathspp.com\/blog\/write-a-coding-agent-from-first-principles-better-tools","content_html":"<p>Improve the capabilities of your agent by providing it with better tools.<\/p>\n\n<h2 id=\"introduction\">Introduction<a href=\"#introduction\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>This tutorial builds on <a href=\"\/blog\/write-a-coding-agent-from-first-principles\">the coding agent you implemented in the tutorial &ldquo;Write a coding agent from first principles&rdquo;<\/a>.\nIn this tutorial, you'll take your agent and improve its capabilities by implementing the text edit and bash command tools that Anthropic provides.<\/p>\n<h2 id=\"why-use-anthropic-s-tools\">Why use Anthropic's tools?<a href=\"#why-use-anthropic-s-tools\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>In the previous tutorial you implemented a coding agent that has a few tools that it can use to read, write, and execute, code.\nThe tool <code>\"bash\"<\/code> can be used to execute arbitrary commands and the tools <code>\"read\"<\/code>, <code>\"write\"<\/code>, <code>\"replace\"<\/code>, and <code>\"insert\"<\/code>, can be used to edit files.<\/p>\n<p>As it turns out, these tools are so universally useful that Anthropic trained its models on specific schema definitions for these tools.\nThe tools still run on the client side, so you'll still get tool use blocks in the API responses, but you don't have to define the schema for the tool.\nYou just specify the tools by <a href=\"https:\/\/platform.claude.com\/docs\/en\/agents-and-tools\/tool-use\/tool-reference#anthropic-provided-tools\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">their Anthropic types and names<\/a>, and the LLMs will happily request tool uses.<\/p>\n<p>Anthropic trains their models on a number of useful tools but you'll focus your attention on two tools that emulate the functionality you already have:<\/p>\n<ol><li><strong>Text editor tool<\/strong>: this tool replaces the four tools you defined to read, write, replace, and insert, text in text files<\/li>\n<li><strong>Bash tool<\/strong>: this tool provides a persistent bash session that can run bash commands<\/li>\n<\/ol><p>By replacing your tools with Anthropic's, the agent will be able to make better tool calls consistently, since Anthropic trains their models on their specific tool schemas.<\/p>\n<h2 id=\"the-native-text-editor-tool\">The native text editor tool<a href=\"#the-native-text-editor-tool\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>To define support for Anthropic's text editor tool you need to add it to your list of tools.\nThe name of the tool is <code>\"str_replace_based_edit_tool\"<\/code> and its type is <code>\"text_editor_20250728\"<\/code>.\n(The type carries <a href=\"https:\/\/platform.claude.com\/docs\/en\/agents-and-tools\/tool-use\/tool-reference#tool-versioning\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">a versioning suffix<\/a> that may influence the tool's behaviour, so make sure you use the right date suffix.)<\/p>\n<p>Since you'll be using Anthropic's text editor tool, you can delete the functions <code>read<\/code>, <code>write<\/code>, <code>replace<\/code>, and <code>insert<\/code>, and the corresponding dictionaries that go in the list <code>TOOLS<\/code>.\nInstead, add the dictionary that specifies the Anthropic tool:<\/p>\n<pre><code class=\"language-py\"># ...\nTOOLS = [\n    {\n        \"type\": \"text_editor_20250728\",\n        \"name\": \"str_replace_based_edit_tool\",\n    }\n]\n\n# Bash tool defined and added later.<\/code><\/pre>\n<p>For organisation purposes, you'll define the text editor tool and the bash tool in their own submodules, so create the folder <code>tools<\/code> and then create the file <code>tools\/str_replace_based_edit_tool.py<\/code> under <code>src\/agent<\/code>.\nIn there, you'll define the code to handle the tool call.<\/p>\n<p>The <a href=\"https:\/\/platform.claude.com\/docs\/en\/agents-and-tools\/tool-use\/text-editor-tool\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">text editor tool<\/a> is a 4-in-1 tool that allows you to <em>view<\/em>, <em>replace<\/em>, <em>create<\/em>, and <em>insert<\/em>, text.\nTo disambiguate the action you want to do, the tool use request includes a <em>command<\/em>:<\/p>\n<pre><code class=\"language-py\"># Example tool use dictionary:\n{\n  \"type\": \"tool_use\",\n  \"id\": \"toolu_01A09q90qw90lq917835lq9\",\n  \"name\": \"str_replace_based_edit_tool\",\n  \"input\": {\n    \"command\": \"view\",  # &lt;--\n    # ...\n  }\n}<\/code><\/pre>\n<p>You'll use the key <code>\"command\"<\/code> from the...<\/p>","summary":"Improve the capabilities of your agent by providing it with better tools.","date_modified":"2026-07-07T09:58:48+02:00","tags":["llms","programming","python"],"image":"\/user\/pages\/02.blog\/write-a-coding-agent-from-first-principles-better-tools\/thumbnail.webp"},{"title":"Write a coding agent from first principles","date_published":"2026-06-22T14:32:00+02:00","id":"https:\/\/mathspp.com\/blog\/write-a-coding-agent-from-first-principles","url":"https:\/\/mathspp.com\/blog\/write-a-coding-agent-from-first-principles","content_html":"<p>Learn how to write a coding agent in this Python tutorial that teaches how to interact with an LLM through an API, how to manage the conversation context, and how to do tool calling.<\/p>\n\n<h2 id=\"introduction\">Introduction<a href=\"#introduction\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>This tutorial will show you how to create your own <strong>coding agent<\/strong> from first principles.\nBy doing so, you'll understand how coding agents work under the hood.<\/p>\n<h2 id=\"prerequisites\">Prerequisites<a href=\"#prerequisites\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>To be able to follow this tutorial, you'll need<\/p>\n<ul><li>\n<strong>prior Python experience<\/strong>: this tutorial is not suitable for people who don't have programming experience<\/li>\n<li>\n<strong>a valid Claude API key<\/strong>: you can get a Claude API key in <a href=\"https:\/\/platform.claude.com\/dashboard\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">the Claude Console dashboard<\/a> [^1]<\/li>\n<li>\n<strong>uv<\/strong>: to manage the project you'll be working on<\/li>\n<\/ul><p>The <em>concepts<\/em> explained in this tutorial are independent from your LLM provider <em>but<\/em> the code snippets will make use of the Claude API and its Python SDK.\nThis means that you can follow along with a different model provider as long as you adapt the code snippets to match the format expected by the API of your provider.<\/p>\n<h2 id=\"what-s-a-coding-agent\">What's a coding agent?<a href=\"#what-s-a-coding-agent\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>A <strong>coding agent<\/strong> is an agent that's specialised for coding.\nIn turn, an agent is just an LLM that has been extended with extra functionality that allows it to interact with its environment.\nThis <em>extra functionality<\/em> is provided through <strong>tools<\/strong>, one of the core ideas covered in this tutorial.<\/p>\n<p>This short definition still hides a lot of details, but instead of giving you a theoretical definition you can <em>learn<\/em> what a coding agent is by creating one.\nThat starts now.<\/p>\n<h2 id=\"project-set-up\">Project set up<a href=\"#project-set-up\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>To set your project up, start by using uv to create a packageable app project:[^2]<\/p>\n<pre><code class=\"language-bash\">% uv init --app --package agent\nInitialized project `agent` at `\/Users\/rodrigogs\/Documents\/mathspp\/agent`<\/code><\/pre>\n<p>Then, <code>cd<\/code> into the project and add the two dependencies you'll need:<\/p>\n<pre><code class=\"language-py\">% cd agent\n% uv add python-dotenv anthropic<\/code><\/pre>\n<p>You'll use <code>python-dotenv<\/code> to help you with authentication to access the Claude API and you'll use the dependency <code>anthropic<\/code> to make it easier to interact with the Claude API.<\/p>\n<p>To set up authentication, create a <code>.env<\/code> file and paste your Claude API key there in front of the variable <code>ANTHROPIC_API_KEY<\/code>.\nWhen you're done, your <code>.env<\/code> file should look like this:<\/p>\n<pre><code class=\"language-text\">ANTHROPIC_API_KEY=\"sk-ant-api03-qI_3mJ...\"<\/code><\/pre>\n<p>To make sure you <em>never<\/em> upload your API key to GitHub by accident, add the file <code>.env<\/code> to your <code>.gitignore<\/code>:<\/p>\n<pre><code class=\"language-text\"># .gitignore\n# ... other entries generated by uv\n.env<\/code><\/pre>\n<p>Now that you've set up your project, you can make your first request to the Claude API.<\/p>\n<h2 id=\"interacting-with-an-llm\">Interacting with an LLM<a href=\"#interacting-with-an-llm\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>A <strong>coding agent<\/strong> needs an LLM at its core.\nYour LLM can come from any provider you want but you're going to use Claude because its SDK (the dependency <code>anthropic<\/code> you added in the previous section) is easy to use and because Claude is a popular model provider.<\/p>\n<p>Using the <code>anthropic<\/code> SDK, here's how you can send a message to the LLM:<\/p>\n<pre><code class=\"language-py\"># src\/agent\/__init__.py\nfrom anthropic import Anthropic\nimport dotenv\n\ndotenv.load_dotenv()  # Load .env\n\nMODEL = \"claude-haiku-4-5\"...<\/code><\/pre>","summary":"Learn how to write a coding agent in this Python tutorial that teaches how to interact with an LLM through an API, how to manage the conversation context, and how to do tool calling.","date_modified":"2026-07-07T09:58:48+02:00","tags":["python","programming","llms"],"image":"\/user\/pages\/02.blog\/write-a-coding-agent-from-first-principles\/thumbnail.webp"},{"title":"TIL #144 \u2013 Sentinel built-in","date_published":"2026-05-01T19:49:00+02:00","id":"https:\/\/mathspp.com\/blog\/til\/sentinel-builtin","url":"https:\/\/mathspp.com\/blog\/til\/sentinel-builtin","content_html":"<p>Today I learned Python 3.15 will get a new sentinel built-in.<\/p>\n\n<p>Sentinel values are unique placeholder values that are commonly used in programming.\nPython 3.15 ships with a new built-in <code>sentinel<\/code> that can be used to create new sentinel values:<\/p>\n<pre><code class=\"language-py\"># Python 3.15+\n&gt;&gt;&gt; MISSING = sentinel(\"MISSING\")\n&gt;&gt;&gt; MISSING\nMISSING<\/code><\/pre>\n<p>Before this built-in was added, the most common sentinel idiom used the built-in <code>object<\/code>:<\/p>\n<pre><code class=\"language-py\">MISSING = object()\n\ndef my_function(some_arg=MISSING):\n    if some_arg is MISSING:\n        ... # Handle the sentinel<\/code><\/pre>\n<p>In the function above, the sentinel value <code>MISSING<\/code> is being used to check whether the user passed <em>anything<\/em> as the parameter <code>some_arg<\/code> or not.\n<a href=\"https:\/\/peps.python.org\/pep-0661\/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">PEP 661<\/a>, that introduced this built-in, has a great discussion covering the reasons as to why this pattern, and many other sentinel patterns, fall short.\nIn general, each common sentinel idiom suffers from at least one of the following problems:<\/p>\n<ol>\n<li><strong>Bad string repr<\/strong>: the <a href=\"\/blog\/pydonts\/str-and-repr\">string representation<\/a> is too long and uninformative<\/li>\n<li><strong>Type unsafe<\/strong>: the sentinels don't have a distinct type so it becomes hard or impossible to write code that uses the sentinels and is type safe<\/li>\n<li><strong>Unexpected copy behaviour<\/strong>: the sentinels can't be copied or pickled without breaking the sentinel behaviour<\/li>\n<\/ol>","summary":"Today I learned Python 3.15 will get a new sentinel built-in.","date_modified":"2026-05-01T21:18:58+02:00","tags":["programming","python"],"image":"\/user\/pages\/02.blog\/04.til\/144.sentinel-builtin\/thumbnail.webp"},{"title":"TIL #143 \u2013 Resolve a lazy import manually","date_published":"2026-04-27T17:18:00+02:00","id":"https:\/\/mathspp.com\/blog\/til\/resolve-a-lazy-import-manually","url":"https:\/\/mathspp.com\/blog\/til\/resolve-a-lazy-import-manually","content_html":"<p>Learn how to work around the Python machinery to resolve an explicit lazy import manually.<\/p>\n\n<p>A couple of articles ago I wrote about how you could <a href=\"\/blog\/til\/inspect-a-lazy-import\">inspect a lazy import<\/a>.<\/p>\n<p>Apparently, you can use a similar trick to check the attributes and methods that a lazy import has:<\/p>\n<pre><code class=\"language-pycon\">&gt;&gt;&gt; lazy import json\n&gt;&gt;&gt; dir(globals()[\"json\"])\n['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'resolve']<\/code><\/pre>\n<p>Apart from a large number of <a href=\"\/blog\/pydonts\/dunder-methods\">dunder methods<\/a> and dunder attributes, you'll find the method <code>resolve<\/code>.\nYou can run <code>help(globals()[\"json\"].resolve)<\/code> to get the help text on that method:<\/p>\n<pre><code class=\"language-text\">Help on built-in function resolve:\n\nresolve() method of builtins.lazy_import instance\n    resolves the lazy import and returns the actual object<\/code><\/pre>\n<p>This shows that it's the method <code>resolve<\/code> that resolves a lazy import.<\/p>\n<p>If you call the method, you can get access to the resolved module:<\/p>\n<pre><code class=\"language-pycon\">&gt;&gt;&gt; lazy import json\n&gt;&gt;&gt; resolved_json = globals()[\"json\"].resolve()\n&gt;&gt;&gt; resolved_json\n&lt;module 'json' from '\/Users\/rodrigogs\/.local\/share\/uv\/python\/cpython-3.15.0a8-macos-aarch64-none\/lib\/python3.15\/json\/__init__.py'&gt;<\/code><\/pre>\n<p>After calling <code>resolve<\/code>, the lazy module doesn't disappear automatically:<\/p>\n<pre><code class=\"language-pycon\">&gt;&gt;&gt; globals()[\"json\"]\n&lt;lazy_import 'json'&gt;<\/code><\/pre>\n<p>Which shows that the mechanism that's responsible for reification <em>most likely<\/em> calls the method <code>resolve<\/code> and then <em>reassigns<\/em> the name of the module to the module returned by <code>resolve<\/code>.\nIn a way, it's as if the reification process ran something like<\/p>\n<pre><code class=\"language-py\">globals()[\"json\"] = globals()[\"json\"].resolve()<\/code><\/pre>\n<p>In hindsight, this isn't too surprising.\nAfter all, Python tends to be very consistent.\nThe only mistery that remains is <em>what<\/em> triggers the reification process.\nHow is it that Python can detect when something <em>touches<\/em> the lazy import..?<\/p>","summary":"Learn how to work around the Python machinery to resolve an explicit lazy import manually.","date_modified":"2026-04-27T18:29:12+02:00","tags":["programming","python"],"image":"\/user\/pages\/02.blog\/04.til\/143.resolve-a-lazy-import-manually\/thumbnail.webp"},{"title":"uv skills for coding agents","date_published":"2026-04-09T14:19:00+02:00","id":"https:\/\/mathspp.com\/blog\/uv-skills","url":"https:\/\/mathspp.com\/blog\/uv-skills","content_html":"<p>This article shares two skills you can add to your coding agents so they use uv workflows.<\/p>\n\n<p>I have fully adopted uv into my workflows and most of the time I want my coding agents to use uv workflows as well, like when running any Python code or managing and running scripts that may or may not have dependencies.<\/p>\n<p>To make this more convenient for me, I created two <code>SKILL.md<\/code> files for two of the most common workflows that the coding agents get wrong on the first few tries:<\/p>\n<ol><li><code>python-via-uv<\/code>: this skill tells the agent that it should use uv whenever it wants to run any piece of Python code, be it one-liners or scripts. This is relevant because I don't even have the command <code>python<\/code>\/<code>python3<\/code> in the shell path, so whenever the LLM tries running something with <code>python ...<\/code>, it fails.<\/li>\n<li><code>uv-script-workflow<\/code>: this skill is specifically for when the agent wants to create and run a script. It instructs the LLM to initalise the script with <code>uv init --script ...<\/code> and then tells it about the relevant commands to manage the script dependencies.<\/li>\n<\/ol><p>The two skills also add a note about sandboxing, since uv's default cache directory will be outside your sandbox.\nWhen that's the case, the agent is already instructed to use a valid temporary location for the uv cache.<\/p>\n<p><em>Installing<\/em> a skill usually just means dropping a Markdown file in the correct folder, but you should check the documentation for the tools you use.<\/p>\n<p>Here are the two skills for you to download:<\/p>\n<ol><li><a href=\"\/blog\/uv-skills\/.\/SKILL-python-via-uv.txt\">Skill for <code>python-via-uv<\/code><\/a><\/li>\n<li><a href=\"\/blog\/uv-skills\/.\/SKILL-uv-script-workflow.txt\">Skill for <code>uv-script-workflow<\/code><\/a><\/li>\n<\/ol><p>I also included the skills verbatim here, for your convenience:<\/p>\n<details><summary>Skill for <code>python-via-uv<\/code><\/summary><pre><code class=\"language-markdown\">---\nname: python-via-uv\ndescription: Enforce Python execution through `uv` instead of direct interpreter calls. Use when Codex needs to run Python scripts, modules, one-liners, tools, test runners, or package commands in a workspace and should avoid invoking `python` or `python3` directly.\n---\n\n# Python Via Uv\n\nUse `uv` for every Python command.\n\nDo not run `python`.\nDo not run `python3`.\nDo not suggest `python` or `python3` in instructions unless the user explicitly requires them and the constraint must be called out as a conflict.\n\n## Execution Rules\n\nWhen sandboxed, set `UV_CACHE_DIR` to a temporary directory the agent can write to before running `uv` commands.\n\nPrefer these patterns:\n\n- Run a script: `UV_CACHE_DIR=\/tmp\/uv-cache uv run path\/to\/script.py`\n- Run a module: `UV_CACHE_DIR=\/tmp\/uv-cache uv run -m package.module`\n- Run a one-liner: `UV_CACHE_DIR=\/tmp\/uv-cache uv run python -c \"print('hello')\"`\n- Run a tool exposed by dependencies: `UV_CACHE_DIR=\/tmp\/uv-cache uv run tool-name`\n- Add a dependency for an ad hoc command: `UV_CACHE_DIR=\/tmp\/uv-cache uv run --with &lt;package&gt; python -c \"...\"`\n\n## Notes\n\nUsing `python` inside `uv run ...` is acceptable because `uv` is still the entrypoint controlling interpreter selection and environment setup.\n\nIf the workspace already defines a project-specific temporary cache directory, prefer that over `\/tmp\/uv-cache`.\n\nIf a command example or existing documentation uses `python` or `python3` directly, translate it to the closest `uv` form before executing it....<\/code><\/pre><\/details>","summary":"This article shares two skills you can add to your coding agents so they use uv workflows.","date_modified":"2026-04-18T17:13:32+02:00","tags":["python","programming","LLM","uv"],"image":"\/user\/pages\/02.blog\/uv-skills\/thumbnail.webp"},{"title":"Indexable iterables","date_published":"2026-04-03T13:41:00+02:00","id":"https:\/\/mathspp.com\/blog\/indexable-iterables","url":"https:\/\/mathspp.com\/blog\/indexable-iterables","content_html":"<p>Learn how objects are automatically iterable if you implement integer indexing.<\/p>\n\n<h2 id=\"introduction\">Introduction<a href=\"#introduction\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>An <strong>iterable<\/strong> in Python is any object you can traverse through with a <code>for<\/code> loop.\n<strong>Iterables<\/strong> are typically containers and iterating over the iterable object allows you to access the elements of the container.<\/p>\n<p>This article will show you how you can create your own iterable objects through the implementation of integer indexing.<\/p>\n<h2 id=\"indexing-with-getitem\">Indexing with <code>__getitem__<\/code><a href=\"#indexing-with-getitem\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>To make an object that can be indexed you need to implement the method <code>__getitem__<\/code>.<\/p>\n<p>As an example, you'll implement a class <code>ArithmeticSequence<\/code> that represents an <strong>arithmetic sequence<\/strong>, like <span class=\"mathjax mathjax--inline\">\\(5, 8, 11, 14, 17, 20\\)<\/span>.\nAn arithmetic sequence is defined by its first number (<span class=\"mathjax mathjax--inline\">\\(5\\)<\/span>), the step between numbers (<span class=\"mathjax mathjax--inline\">\\(3\\)<\/span>), and the total number of elements (<span class=\"mathjax mathjax--inline\">\\(6\\)<\/span>).\nThe sequence <span class=\"mathjax mathjax--inline\">\\(5, 8, 11, 14, 17, 20\\)<\/span> is <code>seq = ArithmeticSequence(5, 3, 6)<\/code> and <code>seq[3]<\/code> should be <span class=\"mathjax mathjax--inline\">\\(14\\)<\/span>.\nUsing some arithmetic, you can implement indexing in <code>__getitem__<\/code> directly:<\/p>\n<pre><code class=\"language-py\">class ArithmeticSequence:\n    def __init__(self, start: int, step: int, total: int) -&gt; None:\n        self.start = start\n        self.step = step\n        self.total = total\n\n    def __getitem__(self, index: int) -&gt; int:\n        if not 0 &lt;= index &lt; self.total:\n            raise IndexError(f\"Invalid index {index}.\")\n\n        return self.start + index * self.step\n\nseq = ArithmeticSequence(5, 3, 6)\nprint(seq[3])  # 14<\/code><\/pre>\n<h2 id=\"turning-an-indexable-object-into-an-iterable\">Turning an indexable object into an iterable<a href=\"#turning-an-indexable-object-into-an-iterable\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>If your object accepts integer indices, then it is <em>automatically<\/em> an iterable.\nIn fact, you can already iterate over the sequence you created above by simply using it in a <code>for<\/code> loop:<\/p>\n<pre><code class=\"language-py\">for value in seq:\n    print(value, end=\", \")\n# 5, 8, 11, 14, 17, 20,<\/code><\/pre>\n<h2 id=\"how-python-distinguishes-iterables-from-non-iterables\">How Python distinguishes iterables from non-iterables<a href=\"#how-python-distinguishes-iterables-from-non-iterables\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>You might ask yourself &ldquo;how does Python inspect <code>__getitem__<\/code> to see it uses numeric indices?&rdquo;\nIt doesn't!\nIf your object implements <code>__getitem__<\/code> and you try to use it as an iterable, Python will <em>try<\/em> to iterate over it.\nIt either works or it doesn't!<\/p>\n<p>To illustrate this point, you can define a class <code>DictWrapper<\/code> that wraps a dictionary and implements <code>__getitem__<\/code> by just grabbing the corresponding item out of a dictionary:<\/p>\n<pre><code class=\"language-py\">class DictWrapper:\n    def __init__(self, values):\n        self.values = values\n\n    def __getitem__(self, index):\n        return self.values[index]<\/code><\/pre>\n<p>Since <code>DictWrapper<\/code> implements <code>__getitem__<\/code>, if an instance of <code>DictWrapper<\/code> just <em>happens<\/em> to have some integer keys (starting at <code>0<\/code>) then you'll be able to iterate partially over the dictionary:<\/p>\n<pre><code class=\"language-py\">d1 = DictWrapper({0: \"hey\", 1: \"bye\", \"key\": \"value\"})\n\nfor value in d1:\n    print(value)<\/code><\/pre>\n<pre><code class=\"language-pycon\">hey\nbye\nTraceback (most recent call last):\n  File \"&lt;python-input-25&gt;\", line 3, in &lt;module&gt;\n    for value in d1:\n                 ^^\n  File \"&lt;python-input-18&gt;\", line 6, in __getitem__\n    return self.values[index]\n           ~~~~~~~~~~~^^^^^^^\nKeyError: 2<\/code><\/pre>\n<p>What's interesting is that you can see explicitly that Python tried to index the object <code>d<\/code> with the key <code>2<\/code> and it didn't work.\nIn the <code>ArithmeticSequence<\/code> above, you didn't get an error because you raised <code>IndexError<\/code> when you reached the end and that's how Python understood the iteration was done.\nIn this case, since you get a <code>KeyError<\/code>, Python doesn't understand what's going on and just...<\/p>","summary":"Learn how objects are automatically iterable if you implement integer indexing.","date_modified":"2026-04-03T15:09:51+02:00","tags":["python","programming","dunder methods"],"image":"\/user\/pages\/02.blog\/indexable-iterables\/thumbnail.webp"},{"title":"Ask the LLM to write code for it","date_published":"2026-03-24T14:16:00+01:00","id":"https:\/\/mathspp.com\/blog\/ask-the-llm-to-write-code-for-it","url":"https:\/\/mathspp.com\/blog\/ask-the-llm-to-write-code-for-it","content_html":"<p>This article covers a useful LLM pattern where you ask the LLM to write code to solve a problem instead of asking it to solve the problem directly.<\/p>\n\n<h2 id=\"the-problem-of-merging-two-transcripts\">The problem of merging two transcripts<a href=\"#the-problem-of-merging-two-transcripts\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>I had two files that contained two halves of the transcript of an audio recording and I wanted to use an LLM to merge the two halves.\nThere were three reasons that stopped me from simply copying part 2 and pasting it after part 1:<\/p>\n<ol>\n<li>the two transcripts overlapped (the end of part 1 was after the start of part 2);<\/li>\n<li>the timestamps for part 2 started from 0, so they were missing an offset; and<\/li>\n<li>speaker identification was not consistent.<\/li>\n<\/ol>\n<p>I uploaded the two halves into ChatGPT and asked it to merge the two transcripts, fix the timestamps and the speaker identification, but to not change the text.<\/p>\n<p>The result I got back was a ridiculous attempt at providing the full transcript, with two sections that supposedly represented parts of either transcript I could just copy and paste confidently, and a couple of other ridiculous blunders.<\/p>\n<p>Instead of fighting ChatGPT, I decided to use a very useful pattern I learned about last year.<\/p>\n<h2 id=\"ask-the-llm-to-write-code-for-it\">Ask the LLM to write code for it<a href=\"#ask-the-llm-to-write-code-for-it\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>Instead of asking ChatGPT to merge the transcripts, I could ask it to analyse them, find the solutions to the three problems listed above, and then write code that would merge the transcripts.<\/p>\n<p>Since I was confident that ChatGPT could<\/p>\n<ol>\n<li>identify the overlap between the two files;<\/li>\n<li>use the overlap information to compute the timestamp offset required for part 2; and<\/li>\n<li>figure out you had to swap the two speakers in part 2,<\/li>\n<\/ol>\n<p>I knew ChatGPT would be able to write a Python script that could read from both files and apply a couple of string operations to the second part.<\/p>\n<p>This yielded much better results in two ways.\nChatGPT was able to find the solutions for the three problems above and write a script that fixed them automatically.\nThat was the goal.<\/p>\n<p>On top of that, since ChatGPT had a very clear implicit goal \u2014 get the final merged transcript \u2014 and since running Python code is something that ChatGPT can do, ChatGPT even ran the script for me and produced two artifacts at the end:<\/p>\n<ol>\n<li>the full Python script I could run against the two halves if I wanted; and<\/li>\n<li>the final, fixed transcript.<\/li>\n<\/ol>\n<p>This is an example application of a really useful LLM pattern:<\/p>\n<blockquote>\n<p>Don't ask the LLM to solve a problem. Instead, ask it to write code that solves the problem.<\/p>\n<\/blockquote>\n<p>As another visual example, it's much easier to ask an LLM to write a Python script that draws a path that solves a maze (that's just a couple hundred of lines of code) than it is to upload an image and ask the LLM to draw a <em>valid path<\/em> on the picture of a maze.\nTry it yourself!<\/p>","summary":"This article covers a useful LLM pattern where you ask the LLM to write code to solve a problem instead of asking it to solve the problem directly.","date_modified":"2026-07-06T16:16:52+02:00","tags":["python","programming","llms","slice of life"],"image":"\/user\/pages\/02.blog\/ask-the-llm-to-write-code-for-it\/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"}]}
