
    
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
                
        
        
        
            
{"version":"https:\/\/jsonfeed.org\/version\/1","title":"mathspp.com feed","home_page_url":"https:\/\/mathspp.com\/blog\/tags\/llm","feed_url":"https:\/\/mathspp.com\/blog\/tags\/llm.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":"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-03-24T15:29:56+01:00","tags":["python","programming","llm","slice of life"],"image":"\/user\/pages\/02.blog\/ask-the-llm-to-write-code-for-it\/thumbnail.webp"},{"title":"Cyclic trapezoid animation","date_published":"2026-03-14T15:51:00+01:00","id":"https:\/\/mathspp.com\/blog\/cyclic-trapezoid-animation","url":"https:\/\/mathspp.com\/blog\/cyclic-trapezoid-animation","content_html":"<p>See an animation of a trapezoid innscribed in a circle, built with some maths and the help of an LLM.<\/p>\n\n<h2 id=\"the-animation\">The animation<a href=\"#the-animation\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>My brother asked for my help to build an animation of a trapezoid inscribed in a circle that kept changing his shape.\nWith <a href=\"\/blog\/til\/cyclic-quadrilateral\">a bit of maths<\/a> and the help of ChatGPT for the UI, I created the animation you can see below.\nUnder the animation you can find a control panel that allows you to tweak some animation parameters, and under that you can find a brief explanation of how the animation works.<\/p>\n<style>\n  .cyclic-trapezoid-embed {\n    --ctp-panel-bg: color-mix(in srgb, var(--bg, #0b1020) 88%, transparent);\n    --ctp-panel-border: color-mix(in srgb, var(--tx, #e5e7eb) 18%, transparent);\n    --ctp-text: var(--tx, #e5e7eb);\n    --ctp-muted: color-mix(in srgb, var(--tx, #e5e7eb) 65%, transparent);\n    --ctp-input-bg: color-mix(in srgb, var(--bg, #0b1020) 92%, black 8%);\n\n    width: 100%;\n    box-sizing: border-box;\n  }\n\n  .cyclic-trapezoid-embed *,\n  .cyclic-trapezoid-embed *::before,\n  .cyclic-trapezoid-embed *::after {\n    box-sizing: border-box;\n  }\n\n  .cyclic-trapezoid-embed .ctp-canvas-wrap {\n    width: 100%;\n  }\n\n  .cyclic-trapezoid-embed canvas {\n    display: block;\n    width: 100%;\n    height: min(70vh, 800px);\n    min-height: 360px;\n    background: var(--bg, #0b1020);\n    border-radius: 16px;\n  }\n\n  .cyclic-trapezoid-embed .ctp-ui {\n    margin-top: 16px;\n    width: 100%;\n    border-radius: 14px;\n    background: var(--ctp-panel-bg);\n    color: var(--ctp-text);\n    border: 1px solid var(--ctp-panel-border);\n    backdrop-filter: blur(8px);\n    box-shadow: 0 12px 30px rgba(0, 0, 0, 0.18);\n    font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;\n    overflow: hidden;\n  }\n\n  .cyclic-trapezoid-embed .ctp-summary {\n    list-style: none;\n    cursor: pointer;\n    padding: 14px;\n    font-size: 18px;\n    font-weight: 700;\n    user-select: none;\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n  }\n\n  .cyclic-trapezoid-embed .ctp-summary::-webkit-details-marker {\n    display: none;\n  }\n\n  .cyclic-trapezoid-embed .ctp-ui-content {\n    padding: 0 14px 14px;\n  }\n\n  .cyclic-trapezoid-embed fieldset {\n    margin: 0 0 12px;\n    padding: 10px 10px 6px;\n    border-radius: 10px;\n    border: 1px solid var(--ctp-panel-border);\n    min-width: 0;\n  }\n\n  .cyclic-trapezoid-embed legend {\n    padding: 0 6px;\n    color: var(--ctp-text);\n    font-weight: 600;\n    font-size: 14px;\n  }\n\n  .cyclic-trapezoid-embed .row {\n    display: grid;\n    grid-template-columns: 1fr auto;\n    gap: 10px;\n    align-items: center;\n    margin-bottom: 8px;\n  }\n\n  .cyclic-trapezoid-embed .row label {\n    font-size: 13px;\n    color: var(--ctp-text);\n  }\n\n  .cyclic-trapezoid-embed .row .value {\n    color: var(--ctp-muted);\n    font-size: 12px;\n    min-width: 64px;\n    text-align: right;\n    font-variant-numeric: tabular-nums;\n  }\n\n  .cyclic-trapezoid-embed .control {\n    display: grid;\n    grid-template-columns: 1fr;\n    gap: 4px;\n    margin-bottom: 10px;\n  }\n\n  .cyclic-trapezoid-embed .control label {\n    font-size: 13px;\n    color: var(--ctp-text);\n  }\n\n  .cyclic-trapezoid-embed input[type=\"range\"],\n  .cyclic-trapezoid-embed input[type=\"number\"],\n  .cyclic-trapezoid-embed input[type=\"color\"] {\n    width: 100%;\n  }\n\n  .cyclic-trapezoid-embed input[type=\"number\"] {\n    padding: 6px 8px;\n    border-radius: 8px;\n    border: 1px solid var(--ctp-panel-border);\n    background: var(--ctp-input-bg);\n    color: var(--ctp-text);\n  }\n\n  .cyclic-trapezoid-embed input[type=\"color\"] {\n    height: 36px;\n    padding: 0;\n    border: none;\n    background: transparent;\n    cursor: pointer;\n  }\n\n  .cyclic-trapezoid-embed .buttons {\n    display: flex;\n    gap: 8px;\n    flex-wrap: wrap;\n  }\n\n  .cyclic-trapezoid-embed button {\n    appearance: none;\n    border: 1px solid var(--ctp-panel-border);\n    background: color-mix(in srgb, var(--bg, #0b1020) 80%, var(--tx, #e5e7eb) 8%);\n    color: var(--ctp-text);\n    border-radius: 10px;\n    padding: 9px 12px;\n    cursor: pointer;\n    font-weight: 600;\n  }\n\n  .cyclic-trapezoid-embed button:hover {\n    background: color-mix(in srgb, var(--bg, #0b1020) 72%, var(--tx, #e5e7eb) 14%);\n  }\n\n  .cyclic-trapezoid-embed .hint {\n    font-size: 12px;\n    color: var(--ctp-muted);\n    line-height: 1.35;\n    margin-top: 6px;\n  }\n\n  .cyclic-trapezoid-embed code {\n    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n  }\n<\/style><div class=\"cyclic-trapezoid-embed\" id=\"cyclic-trapezoid-embed\">\n  <div class=\"ctp-canvas-wrap\">\n    <canvas id=\"ctp-canvas\"><\/canvas><\/div>\n\n  <details class=\"ctp-ui\"><summary class=\"ctp-summary\">Cyclic trapezoid controls<\/summary><div class=\"ctp-ui-content\">\n      <fieldset><legend>Colour<\/legend>\n\n        <div class=\"control\">\n          <label for=\"ctp-bgColor\">Background colour<\/label>\n          <input id=\"ctp-bgColor\" type=\"color\" value=\"#0b1020\"><\/div>\n\n        <div class=\"control\">\n          <label for=\"ctp-circleColor\">Circle colour<\/label>\n          <input id=\"ctp-circleColor\" type=\"color\" value=\"#94a3b8\"><\/div>\n\n        <div class=\"control\">\n          <label for=\"ctp-pointColor\">Points colour<\/label>\n          <input id=\"ctp-pointColor\" type=\"color\" value=\"#f8fafc\"><\/div>\n\n        <div class=\"control\">\n          <label for=\"ctp-parallelColor\">Parallel sides colour<\/label>\n          <input id=\"ctp-parallelColor\" type=\"color\" value=\"#fbbf24\"><\/div>\n\n        <div class=\"control\">\n          <label for=\"ctp-nonParallelColor\">Non-parallel sides colour<\/label>\n          <input id=\"ctp-nonParallelColor\" type=\"color\" value=\"#60a5fa\"><\/div>\n      <\/fieldset><fieldset><legend>Point A<\/legend>\n\n        <div class=\"row\">\n          <label for=\"ctp-aPeriod\">Period<\/label>\n          <div class=\"value\" id=\"ctp-aPeriodValue\"><\/div>\n        <\/div>\n        <input id=\"ctp-aPeriod\" type=\"range\" min=\"2\" max=\"60\" step=\"0.1\" value=\"11\"><\/fieldset><fieldset><legend>Point B<\/legend>\n\n        <div class=\"row\">\n          <label for=\"ctp-bMax\">Max angle<\/label>\n          <div class=\"value\" id=\"ctp-bMaxValue\"><\/div>\n        <\/div>\n        <input id=\"ctp-bMax\" type=\"range\" min=\"15\" max=\"180\" step=\"1\" value=\"165\"><div class=\"row\">\n          <label for=\"ctp-bPeriod\">Period<\/label>\n          <div class=\"value\" id=\"ctp-bPeriodValue\"><\/div>\n        <\/div>\n        <input id=\"ctp-bPeriod\" type=\"range\" min=\"2\" max=\"60\" step=\"0.1\" value=\"7\"><div class=\"row\">\n          <label for=\"ctp-bPhase\">Phase<\/label>\n          <div class=\"value\" id=\"ctp-bPhaseValue\"><\/div>\n        <\/div>\n        <input id=\"ctp-bPhase\" type=\"range\" min=\"0\" max=\"360\" step=\"1\" value=\"0\"><\/fieldset><fieldset><legend>Point D<\/legend>\n\n        <div class=\"row\">\n          <label for=\"ctp-dMax\">Max angle<\/label>\n          <div class=\"value\" id=\"ctp-dMaxValue\"><\/div>\n        <\/div>\n        <input id=\"ctp-dMax\" type=\"range\" min=\"15\" max=\"180\" step=\"1\" value=\"90\"><div class=\"row\">\n          <label for=\"ctp-dPeriod\">Period<\/label>\n          <div class=\"value\" id=\"ctp-dPeriodValue\"><\/div>\n        <\/div>\n        <input id=\"ctp-dPeriod\" type=\"range\" min=\"2\" max=\"60\" step=\"0.1\" value=\"5\"><div class=\"row\">\n          <label for=\"ctp-dPhase\">Phase<\/label>\n          <div class=\"value\" id=\"ctp-dPhaseValue\"><\/div>\n        <\/div>\n        <input id=\"ctp-dPhase\" type=\"range\" min=\"0\" max=\"360\" step=\"1\" value=\"60\"><\/fieldset><fieldset><legend>Global<\/legend>\n\n        <div class=\"row\">\n          <label for=\"ctp-radius\">Circle radius<\/label>\n          <div class=\"value\" id=\"ctp-radiusValue\"><\/div>\n        <\/div>\n        <input id=\"ctp-radius\" type=\"range\" min=\"0.1\" max=\"0.48\" step=\"0.01\" value=\"0.35\"><div class=\"row\">\n          <label for=\"ctp-speed\">Global animation speed<\/label>\n          <div class=\"value\" id=\"ctp-speedValue\"><\/div>\n        <\/div>\n        <input id=\"ctp-speed\" type=\"range\" min=\"0\" max=\"4\" step=\"0.01\" value=\"1\"><\/fieldset><div class=\"buttons\">\n        <button id=\"ctp-resetTime\" type=\"button\">Reset animation time<\/button>\n      <\/div>\n\n      <div class=\"hint\">\n        Point C is computed from the cyclic trapezoid rule:...<\/div><\/div><\/details><\/div>","summary":"See an animation of a trapezoid innscribed in a circle, built with some maths and the help of an LLM.","date_modified":"2026-03-14T17:35:14+01:00","tags":["mathematics","geometry","visualisation","llm"],"image":"\/user\/pages\/02.blog\/cyclic-trapezoid-animation\/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":"Using an LLM to write a countdown timer","date_published":"2025-01-22T19:50:00+01:00","id":"https:\/\/mathspp.com\/blog\/using-an-llm-to-write-a-countdown-timer","url":"https:\/\/mathspp.com\/blog\/using-an-llm-to-write-a-countdown-timer","content_html":"<p>I used an LLM to write a countdown timer and I have mixed feelings about the result.<\/p>\n\n<h2 id=\"using-an-llm-to-write-a-countdown-timer\">Using an LLM to write a countdown timer<a href=\"#using-an-llm-to-write-a-countdown-timer\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>In this article I will tell you the short story of how I used an LLM to write a simple countdown timer for me.\nI will show you the code, what went well, and what went wrong.<\/p>\n<p>The timer we will be building will accept the number of minutes as a command argument:<\/p>\n<pre><code class=\"language-bash\">python cd.py 3<\/code><\/pre>\n<p>And it will then display a countdown timer like the GIF below shows:<\/p>\n<figure class=\"image-caption\"><img title=\"The countdown timer.\" alt=\"A GIF showing a countdown timer from 02:59 to 02:56. The background is white and the time is shown in black.\" src=\"\/user\/pages\/02.blog\/using-an-llm-to-write-a-countdown-timer\/_timer_demo.gif?decoding=auto&amp;fetchpriority=auto\"><figcaption class=\"\">The countdown timer.<\/figcaption><\/figure><h3 id=\"first-iteration\">First iteration<a href=\"#first-iteration\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p>To get the first iteration of the code, here is the prompt I gave to GPT-4o:<\/p>\n<blockquote>\n<p>&ldquo;Write a pygame script that accepts a number of minutes using sys.argv and creates a basic countdown timer displaying the time as 10:00, 09:59, etc, all the way to 00:00.\nThe background should be white and the numbers should be black. Use basic event handling to allow quitting the countdown and pressing space should add another minute to the timer.&rdquo;<\/p>\n<\/blockquote>\n<details><summary>Full code of the first iteration.<\/summary><pre><code class=\"language-py\">import sys\nimport pygame\nfrom pygame.locals import QUIT, KEYDOWN, K_SPACE\n\n## Initialize pygame\npygame.init()\n\n## Screen dimensions and colors\nSCREEN_WIDTH, SCREEN_HEIGHT = 400, 200\nWHITE = (255, 255, 255)\nBLACK = (0, 0, 0)\n\n## Fonts and other settings\nFONT = pygame.font.Font(None, 80)\nFPS = 60\n\n## Parse command-line argument for the timer duration\nif len(sys.argv) != 2 or not sys.argv[1].isdigit():\n    print(\"Usage: python countdown.py &lt;minutes&gt;\")\n    sys.exit(1)\n\nminutes = int(sys.argv[1])\nseconds = minutes * 60\n\n## Set up the display\nscreen = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))\npygame.display.set_caption(\"Countdown Timer\")\n\n## Clock for controlling the frame rate\nclock = pygame.time.Clock()\n\nrunning = True\nwhile running:\n    # Event handling\n    for event in pygame.event.get():\n        if event.type == QUIT:\n            running = False\n        elif event.type == KEYDOWN:\n            if event.key == K_SPACE:\n                seconds += 60\n\n    # Timer logic\n    if seconds &amp;gt; 0:\n        seconds -= 1 \/ FPS\n\n    # Render the timer\n    screen.fill(WHITE)\n    minutes_display = int(seconds \/\/ 60)\n    seconds_display = int(seconds % 60)\n    time_text = f\"{minutes_display:02}:{seconds_display:02}\"\n    text_surface = FONT.render(time_text, True, BLACK)\n    text_rect = text_surface.get_rect(center=(SCREEN_WIDTH \/\/ 2, SCREEN_HEIGHT \/\/ 2))\n    screen.blit(text_surface, text_rect)\n\n    # Update the display\n    pygame.display.flip()\n\n    # Cap the frame rate\n    clock.tick(FPS)\n\n## Quit pygame\npygame.quit()<\/code><\/pre>\n<p><\/p>\n<\/details><p>I know a bit of pygame so I glanced through the code and it look promising enough, so I ran it with <code>python cd.py 3<\/code> and the countdown started.\nI pressed <kbd>SPACE<\/kbd> and the countdown increased by 1 minute.\nEverything looked fine.<\/p>\n<h3 id=\"adding-a-new-feature\">Adding a new feature<a href=\"#adding-a-new-feature\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<p>After this first iteration I wanted to change the code so that, after the countdown was finished, the background and foreground colours flipped each second.\nThis is what I typed:<\/p>\n<blockquote>\n<p>&ldquo;Change the code so that when the timer hits 0, the background and foreground colours reverse each second.&rdquo;<\/p>\n<\/blockquote>\n<p>GPT-4o promptly replied with some edited code.<\/p>\n<details><summary>Full code of the second iteration.<\/summary><pre><code class=\"language-py\">\nimport sys\nimport pygame\nfrom pygame.locals import QUIT, KEYDOWN, K_SPACE\n\n## Initialize pygame\npygame.init()\n\n## Screen dimensions and colors\nSCREEN_WIDTH, SCREEN_HEIGHT = 400, 200\nWHITE = (255,...<\/code><\/pre><\/details>","summary":"I used an LLM to write a countdown timer and I have mixed feelings about the result.","date_modified":"2025-07-23T16:49:02+02:00","tags":["llm","opinion","productivity","programming","pygame","python","scripting"],"image":"\/user\/pages\/02.blog\/using-an-llm-to-write-a-countdown-timer\/thumbnail.webp"},{"title":"TIL #107 \u2013 Automatically activating virtual environments","date_published":"2024-11-19T18:22:00+01:00","id":"https:\/\/mathspp.com\/blog\/til\/automatically-activating-virtual-environments","url":"https:\/\/mathspp.com\/blog\/til\/automatically-activating-virtual-environments","content_html":"<p>Today I learned how to use shell scripting to activate my virtual environment automatically when I change directories.<\/p>\n\n<h2 id=\"automatically-activating-virtual-environments\">Automatically activating virtual environments<a href=\"#automatically-activating-virtual-environments\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>Since I can remember, whenever I <code>cd<\/code> into a directory that contains a virtual environment folder <code>.venv<\/code>, I always want to activate that virtual environment.\nI typed <code>source .venv\/bin\/activate<\/code> so often that I even created an alias so that typing <code>activate<\/code> was enough.<\/p>\n<p>Today, I wondered if there wasn't a way in which I could automatically run the command <code>activate<\/code> whenever I <code>cd<\/code> into a directory that contains a directory <code>.venv<\/code>.\nI was sure there was, so I opened ChatGPT and typed my question:<\/p>\n<blockquote>\n<p>\u201cIn iterm2, how can I run the command <code>activate<\/code> automatically whenever I <code>cd<\/code> into a directory that contains a folder <code>.venv<\/code>?\u201d<\/p>\n<\/blockquote>\n<p>(<a href=\"https:\/\/iterm2.com\/index.html\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">iTerm2<\/a> is the terminal emulator I use.)<\/p>\n<p>The output of the model turned out to be 99% of what I needed, and with a small tweak I ended up with this shell script that I added to my shell configuration file <code>.zshrc<\/code>:<\/p>\n<pre><code class=\"language-zsh\">## Generated by ChatGPT.\n## This will automatically run the command `activate` when I\n## `cd` into a directory that contains a directory `.venv`.\ncd() {\n    builtin cd \"$@\" || return  # Use the built-in cd command and handle errors\n    if [ -d \".venv\" ]; then\n        if [ -f \".venv\/bin\/activate\" ]; then\n            echo \"Activating virtual environment in $(pwd)\"\n            activate\n        else\n            echo \".venv directory found, but no activate script exists.\"\n        fi\n    fi\n}<\/code><\/pre>\n<p>Now, whenever I <code>cd<\/code> into a directory with a <code>.venv<\/code>, I see the message \u201cActivating virtual environment in (...)\u201d and the environment activates!\nIsn't technology wonderful?<\/p>","summary":"Today I learned how to use shell scripting to activate my virtual environment automatically when I change directories.","date_modified":"2025-10-20T22:34:56+02:00","tags":["llm","productivity","python","shell"],"image":"\/user\/pages\/02.blog\/04.til\/107.automatically-activating-virtual-environments\/thumbnail.webp"},{"title":"Automatic site updates with cog and pre-commit","date_published":"2024-08-09T19:00:00+02:00","id":"https:\/\/mathspp.com\/blog\/automatic-site-updates-with-cog-and-pre-commit","url":"https:\/\/mathspp.com\/blog\/automatic-site-updates-with-cog-and-pre-commit","content_html":"<p>This article outlines how I use a pre-commit hook and cog to keep my blog stats updated automatically.<\/p>\n\n<p>Some months ago I introduced some <a href=\"\/blog\">basic stats in my blog<\/a> that show how many articles I've published, along with how many words and lines of code those articles contain.\nI <a href=\"\/blog\/adding-stats-to-my-blog\">wrote an article explaining how I wrote a script that gathers these blog stats for me<\/a> but that's a script I still need to run and then manually update the stats.<\/p>\n<p>I wanted to automate this process but I couldn't bring myself to do it.\nAnd then, I came to the silly realisation that I could use ChatGPT to help me with the set up that I was dreading to do.<\/p>\n<h2 id=\"rewriting-the-stats-with-cog\">Rewriting the stats with cog<a href=\"#rewriting-the-stats-with-cog\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p><a href=\"https:\/\/nedbatchelder.com\/code\/cog\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">Cog<\/a> is &ldquo;a file generation tool&rdquo; that lets you use snippets of Python code to fill in parts of other files.\nFor this particular project, I wanted it to rewrite <a href=\"https:\/\/github.com\/mathspp\/mathspp\/blob\/876da9f7b38708f928e0940247cfa1e10ba8c68a\/pages\/02.blog\/blog.md?plain=1#L16-L18\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">the Markdown table that lists the blog stats<\/a>, that looks like this:<\/p>\n<pre><code class=\"language-markdown\">...\n\n| 364 | 401,577 | 33,251 |\n| :-: | :-: | :-: |\n| articles | words | lines of code |\n\n...<\/code><\/pre>\n<p>I already have a script <code>stats.py<\/code> that computes the stats, so I started by changing it to produce the output in this exact format.\nThen, I tweaked the markdown file to include a cog tag that runs the script and inserts its output in the markdown file.\nThe markdown file now looks like this:<\/p>\n<pre><code class=\"language-markdown\">...\n\n&lt;!--\n[[[cog\nimport cog, subprocess\nresult = subprocess.run([\"python\", \"pages\/02.blog\/stats.py\"], text=True, capture_output=True)\ncog.outl(result.stdout)\n]]]--&gt;\n| 364 | 401,577 | 33,251 |\n| :-: | :-: | :-: |\n| articles | words | lines of code |\n&lt;!--[[[end]]]---&gt;\n\n...<\/code><\/pre>\n<p>The <code>[[[cog<\/code> part tells cog where my Python code starts and the <code>]]]<\/code> tells it where it ends.\nIn my case, I'm just using the module <code>subprocess<\/code> to run the script I already have, I'm capturing its output, and I'm reemitting the output through cog.<\/p>\n<p>The output that I produce with <code>cog.outl<\/code> is then inserted between the <code>]]]<\/code> and the <code>[[[end]]]<\/code>, which is where the table ends up being.\nIn case you're wondering, for every run cog will start by clearing the previous table and then will write the new one.<\/p>\n<p>Finally, note that the cog tags are surrounded by HTML multi-line comments, so that the cog tags themselves do not show up in the final page.\nAt least, not visibly.\nIf you inspect the source code of <a href=\"\/blog\">my blog page<\/a> you will see the HTML comments with the cog tags there.<\/p>\n<h2 id=\"triggering-cog-automatically-with-pre-commit\">Triggering cog automatically with pre-commit<a href=\"#triggering-cog-automatically-with-pre-commit\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>The thing with cog is that I still have to run <code>cog -r blog.md<\/code> to update the table, so my next step is to automate the step that calls cog.\nFor that, I asked ChatGPT to help me set up <a href=\"https:\/\/pre-commit.com\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">a pre-commit hook<\/a> that is triggered when my blog pages change.<\/p>\n<p>After a couple of exchanges with ChatGPT, I ended up with <a href=\"https:\/\/github.com\/mathspp\/mathspp\/blob\/876da9f7b38708f928e0940247cfa1e10ba8c68a\/.pre-commit-config.yaml\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">the following <code>.pre-commit-config.yaml<\/code> file<\/a>...<\/p>","summary":"This article outlines how I use a pre-commit hook and cog to keep my blog stats updated automatically.","date_modified":"2025-07-23T16:49:02+02:00","tags":["git","llm","productivity","programming","python","scripting","slice of life"],"image":"\/user\/pages\/02.blog\/automatic-site-updates-with-cog-and-pre-commit\/thumbnail.webp"},{"title":"ChatGPT for quick prototyping","date_published":"2023-03-20T00:00:00+01:00","id":"https:\/\/mathspp.com\/blog\/chatgpt-for-quick-prototyping","url":"https:\/\/mathspp.com\/blog\/chatgpt-for-quick-prototyping","content_html":"<p>Learn how to use ChatGPT to quickly test and prototype programs.<\/p>\n\n<script>\n(function (window, document) {\n    'use strict';\n\n    let popupTimer = null;\n    const popupDelay = 300;\n    const shareButtonsHTML = `\n    <div id=\"text-selection-sharer\">\n        <button class=\"share-button\" data-platform=\"twitter\"><i class=\"fa fa-twitter\"> Tweet this\n    `;\n\n    document.body.insertAdjacentHTML('beforeend', shareButtonsHTML);\n\n    const sharePopup = document.getElementById('text-selection-sharer');\n\n    const shareUrls = {\n        twitter: (url, text) => `https:\/\/twitter.com\/share?url=${encodeURIComponent(url)}&text=${encodeURIComponent(text)}`,\n    };\n\n    function positionPopup(selection) {\n        const rect = selection.getRangeAt(0).getBoundingClientRect();\n        sharePopup.style.left = `${rect.left}px`;\n        sharePopup.style.top = `${rect.top - 20}px`;\n    }\n\n    function showPopup() {\n        sharePopup.classList.add('visible');\n    }\n\n    function hidePopup() {\n        if (popupTimer) {\n            clearTimeout(popupTimer);\n            popupTimer = null;\n        }\n        sharePopup.classList.remove('visible');\n    }\n\n    function onSelection(e) {\n        const selection = window.getSelection();\n        const selection_text = selection.toString().trim();\n        if (selection_text.length > 0) {\n            positionPopup(selection);\n            showPopup();\n            setTextToBeShared(selection_text);\n        } else {\n            if (!popupTimer) {\n                popupTimer = setTimeout(hidePopup, popupDelay);\n            }\n        }\n    }\n\n    function setTextToBeShared(selectedText) {\n        const currentUrl = window.location.href;\n\n        document.querySelectorAll('.share-button').forEach(button => {\n            const platform = button.getAttribute('data-platform');\n            const shareUrl = platform === 'twitter' ? shareUrls[platform](currentUrl, selectedText) : shareUrls[platform](currentUrl);\n            button.setAttribute('onclick', `window.open('${shareUrl}', '_blank', 'noopener')`);\n        });\n    }\n\n    \/\/ Event listeners\n    document.addEventListener('mouseup', onSelection);\n    document.addEventListener('touchend', onSelection);\n    document.addEventListener('mousedown', () => {\n        if (popupTimer) {\n            clearTimeout(popupTimer);\n            popupTimer = null;\n        }\n    });\n\n})(window, document);\n<\/script><!--\n<script>\ndocument.addEventListener(\"DOMContentLoaded\", function () {\n  const codeBlocks = document.querySelectorAll(\"pre > code\");\n  \/\/ Loop over all code blocks\n  codeBlocks.forEach((codeBlock) => {\n    \/\/ Wrap the code block in a container div\n    const container = document.createElement(\"div\");\n    container.classList.add(\"code-block-container\");\n    \/\/ Add a clipboard icon to the container\n    const clipboard = document.createElement(\"i\");\n    clipboard.classList.add(\"clipboard-icon\", \"far\", \"fa-clipboard\");\n    clipboard.title = \"Copy to clipboard\";\n    container.appendChild(clipboard);\n    \/\/ Move the code block inside the container\n    container.appendChild(codeBlock.cloneNode(true));\n    codeBlock.parentElement.replaceChild(container, codeBlock);\n    \/\/ Copy the code to the clipboard when user clicks the icon\n    clipboard.addEventListener(\"click\", () => {\n      const range = document.createRange();\n      range.selectNodeContents(codeBlock);\n      navigator.clipboard.writeText(codeBlock.textContent);\n      clipboard.title = \"Copied!\";\n    });\n    \/\/ Reset the tooltip on icon mouse leave\n    clipboard.addEventListener(\"mouseleave\", () => {\n      clipboard.title = \"Copy to clipboard\";\n    });\n  });\n});\n<\/script>\n--><script>\ndocument.addEventListener(\"DOMContentLoaded\", function() {\nconst codeBlocks = document.querySelectorAll(\"pre code\");\nconst copyIcon = `<i class=\"far fa-copy fa-lg clipboard-icon\">`;\n\ncodeBlocks.forEach((block) => {\n    const wrapper = document.createElement(\"div\");\n    wrapper.style.position = \"relative\";\n    block.parentNode.insertBefore(wrapper, block);\n    wrapper.appendChild(block);\n    wrapper.insertAdjacentHTML(\"beforeend\", copyIcon);\n    const icon = wrapper.querySelector(\".fa-copy\");\n\n    icon.addEventListener(\"click\", () => {\n        const range = document.createRange();\n        range.selectNodeContents(block);\n        navigator.clipboard.writeText(block.textContent);\n        \/*\n        const tempText = document.createElement(\"textarea\");\n        document.body.appendChild(tempText);\n        tempText.value = block.textContent;\n        tempText.select();\n        document.execCommand(\"copy\");\n        tempText.remove();\n        *\/\n\n        icon.classList.replace(\"fa-copy\", \"fa-check\");\n        setTimeout(() => {\n            icon.classList.replace(\"fa-check\", \"fa-copy\");\n        }, 2000);\n    });\n\n    icon.addEventListener(\"mouseover\", () => {\n        const tooltip = document.createElement(\"p\");\n        tooltip.textContent = \"Copy to clipboard\";\n        tooltip.style.position = \"absolute\";\n        tooltip.style.top = \"30px\";\n        tooltip.style.right = \"0\";\n        tooltip.style.padding = \"3px\";\n        tooltip.style.border = \"1px solid #f8f8f2\";\n        tooltip.style.borderRadius = \"6px\";\n        tooltip.style.fontSize = \"75%\";\n        wrapper.appendChild(tooltip);\n\n        icon.addEventListener(\"mouseout\", () => {\n            tooltip.remove();\n        });\n\n        icon.addEventListener(\"click\", () => {\n            tooltip.textContent = \"Copied!\";\n            setTimeout(() => {\n                tooltip.textContent = \"Copy to clipboard\";\n            }, 2000);\n        });\n    });\n});\n});\n<\/script><style>\n#text-selection-sharer {\n    position: fixed;\n    display: none;\n    padding: 5px;\n    background-color: #1DA1F2;\n    border-radius: 5px;\n    box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);\n    z-index: 1000;\n}\n#text-selection-sharer.visible {\n    display: flex;\n}\n.share-button {\n    cursor: pointer;\n    background: none;\n    border: none;\n    font-size: 14px;\n    padding: 5px 10px;\n    color: #333;\n}\n.share-button:hover {\n    background-color: #f9f9f9;\n}\n\n\/* Clipboard code blocks styling. *\/\n.clipboard-icon {\n  color: #f8f8f2;\n  position: absolute;\n  top: 10px;\n  right: 5px;\n  cursor: pointer;\n  opacity: 0;\n}\n\npre:hover > div > i.clipboard-icon {\n  opacity: 1;\n}\n<\/style><h2 id=\"creating-quick-prototypes\">Creating quick prototypes<a href=\"#creating-quick-prototypes\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p><a href=\"https:\/\/chat.openai.com\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">ChatGPT<\/a> is a large language model developed by OpenAI that has taken the world by storm.\nI have been playing around with ChatGPT a lot and I can say, confidently, that it has made me much more productive.<\/p>\n<p>The examples that follow will show two features that I prototyped quickly for my blog by interacting with ChatGPT-4.\nI will share screenshots of my initial interactions with ChatGPT-4 over the <a href=\"https:\/\/typingmind.com\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">TypingMind<\/a> interface.<\/p>\n<p>The features can be tested out in this article but they will have issues!\nAs I said, these are just prototypes created with the help of ChatGPT!\nIf you find any issues with these features, feel free to reach out to...<\/p>","summary":"Learn how to use ChatGPT to quickly test and prototype programs.","date_modified":"2025-07-23T16:49:02+02:00","tags":["llm","productivity"],"image":"\/user\/pages\/02.blog\/chatgpt-for-quick-prototyping\/thumbnail.webp"},{"title":"Running Python inside ChatGPT","date_published":"2022-12-16T00:00:00+01:00","id":"https:\/\/mathspp.com\/blog\/running-python-inside-chatgpt","url":"https:\/\/mathspp.com\/blog\/running-python-inside-chatgpt","content_html":"<p>Running Python inside ChatGPT shows some amazing capabilities that ChatGPT has to understand Python code and evaluate it for you.<\/p>\n\n<p><img alt=\"Blurred screenshot of my conversation with ChatGPT where ChatGPT simulates an interactive Python REPL.\" src=\"\/user\/pages\/02.blog\/running-python-inside-chatgpt\/thumbnail.webp\"><\/p>\n<h2 id=\"simulating-the-python-repl-inside-chatgpt\">Simulating the Python REPL inside ChatGPT<a href=\"#simulating-the-python-repl-inside-chatgpt\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>Lately, I have been reading some interesting articles about <a href=\"https:\/\/chat.openai.com\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">ChatGPT<\/a>.\nIn one, <a href=\"https:\/\/maximumeffort.substack.com\/p\/i-taught-chatgpt-to-invent-a-language\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">someone invented a new language and had ChatGPT speak it<\/a>.\nIn another, <a href=\"https:\/\/www.engraved.blog\/building-a-virtual-machine-inside\/\" target=\"_blank\" rel=\"nofollow noopener noreferrer\" class=\"external-link no-image\">someone ran a virtual machine inside ChatGPT<\/a>.\nThe latter inspired me to ask this question:<\/p>\n<blockquote>\n<p>Can you run an interactive Python session inside ChatGPT?<\/p>\n<\/blockquote>\n<p>The short answer is &ldquo;yes, you can&rdquo;, and ChatGPT knows quite some Python!\nI played around with ChatGPT, gave it some different prompts and instructions, and then tested ChatGPT's limits and understanding of the Python language.\nI was blown away by ChatGPT's understanding of Python.\nIt understood and gave the correct results for code that used:<\/p>\n<ul><li>\n<a href=\"\/blog\/pydonts\/unpacking-with-starred-assignments\">unpacking with starred assignments<\/a>;<\/li>\n<li>\n<a href=\"\/blog\/pydonts\/deep-unpacking\">deep unpacking<\/a>;<\/li>\n<li>\n<a href=\"\/blog\/pydonts\/list-comprehensions-101\">list comprehensions<\/a>;<\/li>\n<li>iterables and iterators;<\/li>\n<li><a href=\"\/blog\/pydonts\/assignment-expressions-and-the-walrus-operator\">the walrus operator <code>:=<\/code><\/a><\/li>\n<li>decorators;<\/li>\n<li>properties;<\/li>\n<li>\n<a href=\"\/blog\/pydonts\/watch-out-for-recursion\">recursion<\/a>;<\/li>\n<li>huge mathematical operations;<\/li>\n<li>classes;<\/li>\n<li>\n<a href=\"\/blog\/pydonts\/dunder-methods\">dunder methods<\/a>;<\/li>\n<li>asynchronous programming;<\/li>\n<li>among other things!<\/li>\n<\/ul><p>ChatGPT was also able to write code that solved tasks I gave it.<\/p>\n<p>In this article, I will go through excerpts of my conversations with ChatGPT to highlight some of the amazing things that ChatGPT did and some of the mistakes I was able to catch.<\/p>\n<p>If you just want to go ahead and start playing with ChatGPT yourself, here are two simple prompts that you can try to use:<\/p>\n<p><img alt=\"A short transcript of a conversation with ChatGPT where ChatGPT is prompted to simulate a Python REPL.\" src=\"\/user\/pages\/02.blog\/running-python-inside-chatgpt\/_preview.webp\"><\/p>\n<p><img alt=\"A short transcript of a conversation with ChatGPT where ChatGPT is prompted to simulate a Python REPL and also reply to English prompts.\" src=\"\/user\/pages\/02.blog\/running-python-inside-chatgpt\/_preview2.webp\"><\/p>\n<h2 id=\"transcripts\">Transcripts<a href=\"#transcripts\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h2>\n<p>This section will contain the transcripts from conversations between me and ChatGPT.<\/p>\n<blockquote>\n<p>My prompts will be in this colour and I will write exactly what I wrote for ChatGPT.<\/p>\n<\/blockquote>\n<p>My comments will show up as regular text, just like this.<\/p>\n<div class=\"notices yellow\">\n<p>ChatGPT's replies will be in this colour.\nNotice that ChatGPT uses code blocks on its own!\nSo, I will obviously include them.<\/p>\n<\/div>\n<p>Let's start.<\/p>\n<h3 id=\"conversation-1\">Conversation #1<a href=\"#conversation-1\" class=\"toc-anchor after\" data-anchor-icon=\"#\" aria-label=\"Anchor\"><\/a><\/h3>\n<blockquote>\n<p>I want you to simulate a Python interpreter. I will write Python code and your reply will be the REPL prompt, followed by my input, and then the result below. Write no explanations, unless a line ends with #?. If a line ends with #?, add an explanation of the result to your response.<\/p>\n<p>Everything I type is supposed to be Python code that you can execute directly in the REPL. If I need to interact with you, I will start a line with #!. We will resume normal interactions when I exit the REPL.<\/p>\n<\/blockquote>\n<div class=\"notices yellow\">\n<p>Okay, I'll do my best to simulate a Python interpreter for you. Please go ahead and type your Python code.<\/p>\n<pre><code class=\"language-py\">&gt;&gt;&gt; 2 + 2\n4<\/code><\/pre>\n<pre><code class=\"language-py\">&gt;&gt;&gt; \"hello\" + \"world\"\n'helloworld'<\/code><\/pre>\n<pre><code class=\"language-py\">&gt;&gt;&gt; [1, 2, 3] * 3\n[1, 2, 3, 1, 2, 3, 1, 2, 3]<\/code><\/pre>\n<pre><code class=\"language-py\">&gt;&gt;&gt; #?\n# The \"#?\" at the end of this line is a comment indicating that you\n# would like me to provide an explanation of the result. In ! this\n# case, there is no result since this line is just a comment and does\n# not contain any valid Python code.<\/code><\/pre>\n<pre><code class=\"language-py\">&gt;&gt;&gt; def add(a,...<\/code><\/pre><\/div>","summary":"Running Python inside ChatGPT shows some amazing capabilities that ChatGPT has to understand Python code and evaluate it for you.","date_modified":"2025-07-23T16:49:02+02:00","tags":["llm","machine learning","programming","python"],"image":"\/user\/pages\/02.blog\/running-python-inside-chatgpt\/thumbnail.webp"}]}
