<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Pradeep Kumar</title>
    <description>The latest articles on DEV Community by Pradeep Kumar (@pradeep_kumar_bc4e7e9f7ec).</description>
    <link>https://dev.to/pradeep_kumar_bc4e7e9f7ec</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4020864%2F3dda0ab9-b27c-4cee-a1ee-d34067f9bab9.png</url>
      <title>DEV Community: Pradeep Kumar</title>
      <link>https://dev.to/pradeep_kumar_bc4e7e9f7ec</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pradeep_kumar_bc4e7e9f7ec"/>
    <language>en</language>
    <item>
      <title>Testing with pytest: Fixtures, Fake Models, and Properties</title>
      <dc:creator>Pradeep Kumar</dc:creator>
      <pubDate>Sat, 24 Oct 2026 08:22:41 +0000</pubDate>
      <link>https://dev.to/pradeep_kumar_bc4e7e9f7ec/testing-with-pytest-fixtures-fake-models-and-properties-1987</link>
      <guid>https://dev.to/pradeep_kumar_bc4e7e9f7ec/testing-with-pytest-fixtures-fake-models-and-properties-1987</guid>
      <description>&lt;p&gt;Here's a feeling you'll know soon enough. You change one small thing in your code, and you're not sure what else you just broke. So you run the program by hand, try a couple of inputs, and hope.&lt;/p&gt;

&lt;p&gt;Tests replace the hoping. A test is a small piece of code that checks another piece of code, and you can run hundreds of them in a second. This post is core, and it matters even more for AI work than for ordinary programming, because AI apps have a part that's unpredictable (the model) surrounded by a lot of code that isn't. That surrounding code is what we'll test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tests are not evals
&lt;/h2&gt;

&lt;p&gt;Before we start, one distinction that will save you confusion later.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tests&lt;/strong&gt; check &lt;em&gt;your code&lt;/em&gt;. Given this input, does the function return that output? They're fast, free, and always give the same answer.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Evals&lt;/strong&gt; measure &lt;em&gt;how good the model's answers are&lt;/em&gt;. They call a real model, the results vary, and they take real time and money. That's Post 60.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You need both. This post is about the first kind. And the trick for testing code that talks to a model is to replace the model with a fake, which is the heart of this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your first test
&lt;/h2&gt;

&lt;p&gt;We'll use the &lt;code&gt;textkit&lt;/code&gt; package from Post 12. Install pytest in your project's environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;pytest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you set up the &lt;code&gt;dev&lt;/code&gt; extras in Post 12, &lt;code&gt;pip install -e ".[dev]"&lt;/code&gt; does it for you. With uv, it's &lt;code&gt;uv add --dev pytest&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Create a file called &lt;code&gt;tests/test_counting.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;textkit.counting&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;count_words&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_words&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_counts_repeated_words&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;count_words&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;the cat the dog&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dog&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_ignores_case_and_punctuation&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;count_words&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The cat. the CAT,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_top_words_returns_at_most_n&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;top_words&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a b c d e f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_top_words_orders_by_count&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;top_words&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x y y z z z&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;z&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;y&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pytest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see something like this (your version numbers and timing will differ):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;=============================&lt;/span&gt; &lt;span class="nb"&gt;test &lt;/span&gt;session starts &lt;span class="o"&gt;==============================&lt;/span&gt;
collected 4 items

tests/test_counting.py ....                                              &lt;span class="o"&gt;[&lt;/span&gt;100%]

&lt;span class="o"&gt;==============================&lt;/span&gt; 4 passed &lt;span class="k"&gt;in &lt;/span&gt;0.02s &lt;span class="o"&gt;===============================&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each dot is a passing test. That's the whole system. Two rules make pytest find your tests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Files are named &lt;code&gt;test_something.py&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Test functions are named &lt;code&gt;test_something&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And a test is just a function with a plain &lt;code&gt;assert&lt;/code&gt; in it. If the assert is true, the test passes. If it's false, the test fails.&lt;/p&gt;

&lt;h3&gt;
  
  
  When a test fails
&lt;/h3&gt;

&lt;p&gt;Say we break a test on purpose by changing the expected length to 4. Here's the kind of message pytest gives you:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_top_words_returns_at_most_n&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;top_words&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a b c d e f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;       &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="n"&gt;E&lt;/span&gt;       &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="n"&gt;E&lt;/span&gt;        &lt;span class="o"&gt;+&lt;/span&gt;  &lt;span class="n"&gt;where&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;([(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;c&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)])&lt;/span&gt;

&lt;span class="n"&gt;tests&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;test_counting&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;AssertionError&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice it doesn't just say "failed." It shows the line, the values on both sides, and where the number came from. That's a big part of why people like pytest. You rarely need extra print statements to understand a failure.&lt;/p&gt;

&lt;p&gt;One practical note. The tests import &lt;code&gt;textkit&lt;/code&gt;, which works because you installed the package in editable mode in Post 12. If you'd rather not, you can add this to &lt;code&gt;pyproject.toml&lt;/code&gt; so pytest finds your code in &lt;code&gt;src/&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="k"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="k"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ini_options&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;testpaths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"tests"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;pythonpath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"src"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What makes a good test
&lt;/h2&gt;

&lt;p&gt;A few habits, learned the hard way by lots of programmers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;One idea per test.&lt;/strong&gt; If a test fails, the name should tell you what broke. &lt;code&gt;test_ignores_case_and_punctuation&lt;/code&gt; is much better than &lt;code&gt;test_stuff&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Arrange, act, assert.&lt;/strong&gt; Set things up, do the thing, check the result. Keeping those three steps visible makes tests easy to read.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Test behavior, not insides.&lt;/strong&gt; Check what a function returns or does, not how it does it. Then you can rewrite the inside and the tests still pass.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cover the edges.&lt;/strong&gt; Empty input, a single item, very long input, odd characters, zero, negative numbers. Bugs hide at the edges.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Keep them fast and independent.&lt;/strong&gt; A test shouldn't rely on another test running first.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every time you find a bug, write a test that fails because of it, &lt;em&gt;then&lt;/em&gt; fix the bug. It's the "lock it in" step from Post 11, and it means the same bug can never sneak back unnoticed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing that an error is raised
&lt;/h2&gt;

&lt;p&gt;Sometimes the correct behavior is to fail. Remember &lt;code&gt;set_temperature&lt;/code&gt; from Post 11? Here's how you check it raises an error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_temperature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;temperature must be between 0 and 2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_temperature_rejects_out_of_range&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;temperature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;set_temperature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;pytest.raises&lt;/code&gt; is a context manager (Post 8). The test passes only if the code inside the &lt;code&gt;with&lt;/code&gt; block raises that error. The optional &lt;code&gt;match&lt;/code&gt; checks the message too. If no error happens, the test fails.&lt;/p&gt;

&lt;h2&gt;
  
  
  Many cases, one test: parametrize
&lt;/h2&gt;

&lt;p&gt;You often want to run the same check on lots of inputs. Instead of copying the test, use a decorator with arguments (Post 8 again):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;textkit.counting&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;count_words&lt;/span&gt;

&lt;span class="nd"&gt;@pytest.mark.parametrize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text, expected&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{}),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;A a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_count_words_edge_cases&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;count_words&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pytest runs this function three times, once per row, and reports each one separately. Adding a new case is one new line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixtures: reusable setup
&lt;/h2&gt;

&lt;p&gt;A lot of tests need the same starting point: a file to read, a configured object, a fake service. A &lt;strong&gt;fixture&lt;/strong&gt; is a function that builds that thing, and pytest hands it to any test that asks for it by name.&lt;/p&gt;

&lt;p&gt;Remember the text pipeline from Post 7? To test it, we need a file. Pytest has a built-in fixture called &lt;code&gt;tmp_path&lt;/code&gt; that gives you a fresh temporary folder, and we can build our own fixture on top of it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pipeline&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;keep_real_text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;read_lines&lt;/span&gt;

&lt;span class="nd"&gt;@pytest.fixture&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;temp_notes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tmp_path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tmp_path&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;notes.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;line one is long enough to keep&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;short&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_pipeline_keeps_only_long_lines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temp_notes&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;keep_real_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;read_lines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temp_notes&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;line one is long enough to keep&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See how it works. The test has a parameter called &lt;code&gt;temp_notes&lt;/code&gt;. Pytest notices that name, finds the fixture with the same name, runs it, and passes in the result. Each test gets a fresh one, and the temporary folder is cleaned up afterward.&lt;/p&gt;

&lt;p&gt;(One heads-up. If your &lt;code&gt;pipeline.py&lt;/code&gt; from Post 7 has code at the bottom that runs the pipeline, importing it in a test will run that code too. That's exactly what the &lt;code&gt;if __name__ == "__main__":&lt;/code&gt; guard from Post 12 is for. Wrap the bottom part in it.)&lt;/p&gt;

&lt;p&gt;If several test files need the same fixture, put it in a file called &lt;code&gt;conftest.py&lt;/code&gt; in the &lt;code&gt;tests/&lt;/code&gt; folder. Pytest picks it up automatically, with no import needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  monkeypatch: changing things temporarily
&lt;/h3&gt;

&lt;p&gt;Another built-in fixture, &lt;code&gt;monkeypatch&lt;/code&gt;, lets a test change something and automatically puts it back afterward. It's perfect for environment variables, like the API key from Post 12:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_api_key&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MODEL_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Set the MODEL_API_KEY environment variable first&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_missing_key_raises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;monkeypatch&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;monkeypatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MODEL_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;raising&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;get_api_key&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_key_is_read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;monkeypatch&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;monkeypatch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MODEL_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;test-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;get_api_key&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;test-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both tests work no matter what's set on your computer, and neither leaves a mess behind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing code that calls a model
&lt;/h2&gt;

&lt;p&gt;Now the part you came for. Suppose you have a function that asks a model for JSON and retries if the reply isn't valid. Testing it against a real model would be slow, cost money, need an API key, and give a different answer each time. You can't test logic that way.&lt;/p&gt;

&lt;p&gt;The fix is to make the model something you can swap out, which is exactly what we set up in Post 9 with composition and protocols. Here's the function (add it as &lt;code&gt;src/textkit/replies.py&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_json_reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_attempts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Ask the model for JSON. Retry if the reply isn&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t valid JSON.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_attempts&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;reply&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;JSONDecodeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Your last reply was not valid JSON. Reply with JSON only.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Model never returned valid JSON&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It takes &lt;code&gt;model&lt;/code&gt; as a parameter, and all it needs is something with a &lt;code&gt;generate&lt;/code&gt; method. So in tests, we hand it a &lt;strong&gt;fake&lt;/strong&gt;: a tiny class that returns replies we chose in advance.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;FakeModel&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;A stand-in for a real model that returns canned replies.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;replies&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replies&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;replies&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;calls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It also records every prompt it received in &lt;code&gt;self.calls&lt;/code&gt;, so tests can check what we sent. Now the tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;textkit.replies&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;get_json_reply&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_returns_parsed_json&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FakeModel&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: 42}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;get_json_reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Give me JSON&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_retries_after_bad_json&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FakeModel&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;not json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: 42}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_json_reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Give me JSON&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;not valid JSON&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_gives_up_after_max_attempts&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FakeModel&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bad&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bad&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;bad&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raises&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;valid JSON&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;get_json_reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Give me JSON&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These tests run instantly, cost nothing, and are the same every time. And they check real things: that good JSON is parsed, that a bad reply triggers a retry, that the retry prompt includes the correction message, and that we give up eventually instead of looping forever.&lt;/p&gt;

&lt;h3&gt;
  
  
  When you can't pass a fake in: mock and patch
&lt;/h3&gt;

&lt;p&gt;Sometimes the model call is buried inside code you can't easily change. Python's built-in &lt;code&gt;unittest.mock&lt;/code&gt; library has tools for that, and they work fine inside pytest.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Mock&lt;/code&gt; makes an object that accepts any call and remembers it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;unittest.mock&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Mock&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_works_with_a_mock_object&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Mock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;return_value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: 1}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;get_json_reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;answer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assert_called_once_with&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;side_effect&lt;/code&gt; lets a mock fail or change its answer from call to call. This is great for testing retries without waiting around:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call_with_retries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attempts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;TimeoutError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_retries_then_succeeds&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;flaky&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;side_effect&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;TimeoutError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;slow&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;call_with_retries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flaky&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ok&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;flaky&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call_count&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;patch&lt;/code&gt; temporarily replaces a real function by name. Imagine &lt;code&gt;my_app.py&lt;/code&gt; has a function &lt;code&gt;call_api&lt;/code&gt; that hits the network, and &lt;code&gt;answer&lt;/code&gt; that uses it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# my_app.py
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;call_api&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;  &lt;span class="c1"&gt;# the real network call
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;call_api&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;unittest.mock&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;patch&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;my_app&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_answer_strips_whitespace&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;patch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my_app.call_api&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;return_value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  hi  &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;my_app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;q&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hi&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The one rule people forget: &lt;strong&gt;patch the name where it's used, not where it was defined.&lt;/strong&gt; Here &lt;code&gt;answer&lt;/code&gt; looks up &lt;code&gt;call_api&lt;/code&gt; inside &lt;code&gt;my_app&lt;/code&gt;, so that's the name we replace. Pytest's &lt;code&gt;monkeypatch.setattr(my_app, "call_api", lambda prompt: " hi ")&lt;/code&gt; does the same job if you prefer it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Don't overdo it
&lt;/h3&gt;

&lt;p&gt;Mocks are powerful, and it's easy to go too far. A few cautions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fake only at the edges&lt;/strong&gt;: the model, the network, the clock. Don't mock your own internal functions, or you'll end up testing the mocks instead of your code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Fakes can drift.&lt;/strong&gt; If the real model API changes and your fake doesn't, your tests pass while your app breaks. Keep a small number of tests that call the real thing, mark them, and run them only occasionally:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="k"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="k"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ini_options&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;markers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"integration: tests that call a real model API (slow, cost money)"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@pytest.mark.integration&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_real_model_returns_json&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then &lt;code&gt;pytest -m "not integration"&lt;/code&gt; runs everything except those, and &lt;code&gt;pytest -m integration&lt;/code&gt; runs only them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Tools that record and replay&lt;/strong&gt; real API responses also exist, if you want realistic data without hitting the network each time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Property-based testing: let the computer find the weird inputs
&lt;/h2&gt;

&lt;p&gt;Ordinary tests check examples you thought of. The trouble is that bugs live in the examples you &lt;em&gt;didn't&lt;/em&gt; think of. Property-based testing flips it around. You describe a rule that should be true for &lt;em&gt;any&lt;/em&gt; input, and a tool generates hundreds of inputs to try to break it.&lt;/p&gt;

&lt;p&gt;The standard library for this in Python is &lt;strong&gt;Hypothesis&lt;/strong&gt;. Install it with &lt;code&gt;pip install hypothesis&lt;/code&gt;. Here are two properties of our word counter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;hypothesis&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;given&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;hypothesis&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;strategies&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;textkit.counting&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;count_words&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_words&lt;/span&gt;

&lt;span class="nd"&gt;@given&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;integers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;min_value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_value&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_top_words_is_limited_and_sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;top_words&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
    &lt;span class="n"&gt;counts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;counts&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@given&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_adding_a_word_adds_one_to_its_count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;count_words&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;zzz&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;count_words&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; zzz&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;zzz&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;after&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;before&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;@given(...)&lt;/code&gt; tells Hypothesis what kinds of inputs to create: any text, any whole number between 0 and 10. It then runs your test many times with different values, including nasty ones like empty strings and odd Unicode characters.&lt;/p&gt;

&lt;p&gt;Notice we never said what the output should be for any particular input. We said what must &lt;em&gt;always&lt;/em&gt; hold: the result is never longer than &lt;code&gt;n&lt;/code&gt;, the counts are sorted from biggest to smallest, and adding a word once raises its count by exactly one.&lt;/p&gt;

&lt;p&gt;When Hypothesis finds a failing input, it doesn't just show you the mess it started with. It shrinks it to the smallest failing example, which is usually much easier to understand. A function that divides by &lt;code&gt;len(numbers)&lt;/code&gt;, for example, would be caught almost instantly with the empty list &lt;code&gt;[]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Properties are harder to write than examples, so don't use them for everything. They shine for code with clear rules: parsers, formatters, sorting, batching, and anything where you can say "convert it and convert it back, and you should get the original."&lt;/p&gt;

&lt;h2&gt;
  
  
  Running tests well
&lt;/h2&gt;

&lt;p&gt;A few options you'll use constantly:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;pytest&lt;/td&gt;
&lt;td&gt;Run everything&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pytest -v&lt;/td&gt;
&lt;td&gt;Show each test name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pytest -x&lt;/td&gt;
&lt;td&gt;Stop at the first failure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pytest -k counts&lt;/td&gt;
&lt;td&gt;Run only tests whose name contains "counts"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pytest tests/test_counting.py::test_top_words_orders_by_count&lt;/td&gt;
&lt;td&gt;Run one specific test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pytest --lf&lt;/td&gt;
&lt;td&gt;Re-run only the tests that failed last time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pytest -s&lt;/td&gt;
&lt;td&gt;Show print output, which pytest hides by default&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pytest --pdb&lt;/td&gt;
&lt;td&gt;Drop into the debugger from Post 11 when a test fails&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Later, in Post 28, we'll set up automatic runs so your tests execute every time you push code. A test suite that only runs when you remember is a test suite that stops running.&lt;/p&gt;

&lt;p&gt;One last warning. You may hear about "test coverage," a percentage showing how many lines your tests ran. It's a handy way to spot code that has no tests at all. But a high number doesn't mean good tests. You can run every line and check nothing. Aim for tests that would actually catch a mistake.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mini project: a test suite for textkit
&lt;/h2&gt;

&lt;p&gt;Let's add a real test suite to the &lt;code&gt;textkit&lt;/code&gt; package from Post 12.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1.&lt;/strong&gt; Add the &lt;code&gt;replies.py&lt;/code&gt; module shown above to &lt;code&gt;src/textkit/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2.&lt;/strong&gt; Update the project settings in &lt;code&gt;pyproject.toml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="n"&gt;project&lt;/span&gt;&lt;span class="k"&gt;.&lt;/span&gt;&lt;span class="n"&gt;optional-dependencies&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;dev&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"pytest&amp;gt;=8.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"hypothesis&amp;gt;=6.0"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tool&lt;/span&gt;&lt;span class="k"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pytest&lt;/span&gt;&lt;span class="k"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ini_options&lt;/span&gt;&lt;span class="k"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;testpaths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"tests"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;markers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"integration: tests that call a real model API (slow, cost money)"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3.&lt;/strong&gt; Install everything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;".[dev]"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 4.&lt;/strong&gt; Create three test files in &lt;code&gt;tests/&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;test_counting.py&lt;/code&gt;: the four example tests from earlier, plus the parametrized edge-case test.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;test_replies.py&lt;/code&gt;: the &lt;code&gt;FakeModel&lt;/code&gt; class, the three tests that use it, and the &lt;code&gt;Mock&lt;/code&gt; test.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;test_properties.py&lt;/code&gt;: the two Hypothesis tests.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 5.&lt;/strong&gt; Run &lt;code&gt;pytest&lt;/code&gt;. You should see all 13 tests pass:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tests/test_counting.py .......                                           &lt;span class="o"&gt;[&lt;/span&gt; 53%]
tests/test_properties.py ..                                              &lt;span class="o"&gt;[&lt;/span&gt; 69%]
tests/test_replies.py ....                                               &lt;span class="o"&gt;[&lt;/span&gt;100%]

&lt;span class="o"&gt;==============================&lt;/span&gt; 13 passed &lt;span class="k"&gt;in &lt;/span&gt;0.31s &lt;span class="o"&gt;==============================&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(The seven in the first file are four examples plus three parametrized cases.)&lt;/p&gt;

&lt;p&gt;Now the most important step: &lt;strong&gt;try to break it.&lt;/strong&gt; Open &lt;code&gt;counting.py&lt;/code&gt; and make a deliberate mistake, such as removing &lt;code&gt;.lower()&lt;/code&gt;, flipping &lt;code&gt;reverse=True&lt;/code&gt; to &lt;code&gt;reverse=False&lt;/code&gt;, or changing &lt;code&gt;ranked[:n]&lt;/code&gt; to &lt;code&gt;ranked[:n + 1]&lt;/code&gt;. Run the tests and watch which ones catch it. A test that never fails isn't protecting you. Then put the code back.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make it yours
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Test the command line tool. Using &lt;code&gt;monkeypatch.setattr("sys.argv", ["textkit", "the", "cat", "the"])&lt;/code&gt; and the &lt;code&gt;capsys&lt;/code&gt; fixture, call &lt;code&gt;main()&lt;/code&gt; and check that the printed output contains &lt;code&gt;the: 2&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add an integration test, marked with &lt;code&gt;@pytest.mark.integration&lt;/code&gt;, that would call a real model. Confirm &lt;code&gt;pytest -m "not integration"&lt;/code&gt; skips it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Install &lt;code&gt;pytest-cov&lt;/code&gt; and run &lt;code&gt;pytest --cov=textkit&lt;/code&gt;. Look at which lines have no tests.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write a fake model that raises an error on the first call, and test how &lt;code&gt;get_json_reply&lt;/code&gt; should behave.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Practice
&lt;/h2&gt;

&lt;p&gt;About 20 minutes of real effort on each before asking for help.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Write five tests for &lt;code&gt;safe_int&lt;/code&gt; from Post 11, using &lt;code&gt;parametrize&lt;/code&gt; for the different inputs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write a test for &lt;code&gt;set_temperature&lt;/code&gt; that uses &lt;code&gt;pytest.raises&lt;/code&gt; with a &lt;code&gt;match&lt;/code&gt;, and add a second test for a value on the boundary (exactly 0 and exactly 2).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write a fixture that creates a temporary file, and use it to test &lt;code&gt;read_lines&lt;/code&gt; from Post 7.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Take an earlier project that retries an API call. Replace the real call with a &lt;code&gt;Mock(side_effect=[...])&lt;/code&gt; and test the retry logic without waiting.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write a property-based test for sorting: the result is in order, and it has the same items as the input.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Break a function on purpose, confirm a test fails with a readable message, then fix it.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then ask an AI tutor to review your tests, and don't let it write them for you. Tests you didn't think through are the ones that give false comfort.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to take away
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Tests check your code and give the same answer every time. Evals check the model's quality, and come later.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Name files &lt;code&gt;test_*.py&lt;/code&gt; and functions &lt;code&gt;test_*&lt;/code&gt;, and use plain &lt;code&gt;assert&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;pytest.raises&lt;/code&gt; checks for errors, and &lt;code&gt;parametrize&lt;/code&gt; runs one test on many inputs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fixtures provide reusable setup, and &lt;code&gt;tmp_path&lt;/code&gt; and &lt;code&gt;monkeypatch&lt;/code&gt; are two built-in ones you'll use a lot.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;To test code that uses a model, swap in a fake. Pass it in as a parameter, or use &lt;code&gt;Mock&lt;/code&gt; and &lt;code&gt;patch&lt;/code&gt; when you can't.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Patch where a name is used, fake only at the edges, and keep a few real integration tests.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Property-based tests describe rules that must always hold, and let Hypothesis hunt for the inputs that break them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A test is only useful if it can fail, so try breaking your code on purpose.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Previous:&lt;/strong&gt; Post 12, &lt;em&gt;Packaging &amp;amp; Environments&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next in the series:&lt;/strong&gt; Post 14, &lt;em&gt;Concurrency&lt;/em&gt;, where we cover threads, processes, &lt;code&gt;asyncio&lt;/code&gt;, and why LLM apps are so often async.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Published via &lt;a href="https://zyvop.com/testing-with-pytest-fixtures-fake-models-and-properties-edvab?utm_source=devto&amp;amp;utm_medium=crosspost&amp;amp;utm_campaign=syndication" rel="noopener noreferrer"&gt;ZyVOP&lt;/a&gt; — Write once in Markdown, auto-backup to GitHub, and syndicate to Dev.to, Medium &amp;amp; Hashnode in 1 click.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aiengineer</category>
      <category>mocking</category>
      <category>pytest</category>
      <category>python</category>
    </item>
    <item>
      <title>Packaging &amp; Environments: Modules, Virtual Environments, and pyproject.toml</title>
      <dc:creator>Pradeep Kumar</dc:creator>
      <pubDate>Fri, 23 Oct 2026 06:13:26 +0000</pubDate>
      <link>https://dev.to/pradeep_kumar_bc4e7e9f7ec/packaging-environments-modules-virtual-environments-and-pyprojecttoml-59ac</link>
      <guid>https://dev.to/pradeep_kumar_bc4e7e9f7ec/packaging-environments-modules-virtual-environments-and-pyprojecttoml-59ac</guid>
      <description>&lt;p&gt;Every programmer eventually says the same sentence: "But it works on my machine." Usually it means two computers have different versions of Python, or different libraries installed, or both.&lt;/p&gt;

&lt;p&gt;This post is about fixing that, and it's one of the least glamorous and most useful things you'll learn. It's core. By the end you'll know how to split code into files, keep each project's libraries separate, and describe a project so anyone can install it. We'll also fix the habit from Post 10, where I told you to just run &lt;code&gt;pip install&lt;/code&gt; and promised a tidier way later. This is the tidier way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Modules: a file is a module
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;module&lt;/strong&gt; is just a Python file. When one file uses code from another, it imports it. You've been doing this with built-in modules like &lt;code&gt;time&lt;/code&gt; and &lt;code&gt;logging&lt;/code&gt; already. Now let's do it with your own.&lt;/p&gt;

&lt;p&gt;Make a file called &lt;code&gt;helpers.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;shout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And another called &lt;code&gt;main.py&lt;/code&gt; in the same folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;helpers&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;helpers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;shout&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;helpers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;shout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hi&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;    &lt;span class="c1"&gt;# HI!
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;shout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;         &lt;span class="c1"&gt;# HELLO!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are two ways to import. &lt;code&gt;import helpers&lt;/code&gt; brings in the whole module, and you use &lt;code&gt;helpers.shout&lt;/code&gt;. &lt;code&gt;from helpers import shout&lt;/code&gt; brings in just that one name. Both are fine. Many people also shorten long names with &lt;code&gt;as&lt;/code&gt;, like &lt;code&gt;import numpy as np&lt;/code&gt;, which you'll see everywhere later.&lt;/p&gt;

&lt;p&gt;A habit worth building: don't write &lt;code&gt;from something import *&lt;/code&gt;. It dumps every name from the module into your file, and then nobody, including you, can tell where a name came from.&lt;/p&gt;

&lt;h3&gt;
  
  
  The &lt;code&gt;__name__&lt;/code&gt; trick
&lt;/h3&gt;

&lt;p&gt;Here's a pattern you'll see at the bottom of almost every script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;shout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;shout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;testing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every module has a built-in variable called &lt;code&gt;__name__&lt;/code&gt;. When you run a file directly with &lt;code&gt;python helpers.py&lt;/code&gt;, Python sets it to &lt;code&gt;"__main__"&lt;/code&gt;. When another file &lt;em&gt;imports&lt;/em&gt; it, it's set to the module's name instead, which is &lt;code&gt;"helpers"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So the &lt;code&gt;if&lt;/code&gt; block runs when you run the file, and stays quiet when someone imports it. That's how one file can be both a reusable module and a runnable script.&lt;/p&gt;

&lt;h3&gt;
  
  
  A mistake that confuses everyone once
&lt;/h3&gt;

&lt;p&gt;Python looks for imports in your current folder &lt;em&gt;first&lt;/em&gt;. So if you name your own file after a standard library module, yours wins, and things break in confusing ways.&lt;/p&gt;

&lt;p&gt;Say you save a file as &lt;code&gt;json.py&lt;/code&gt;, then in another file in the same folder you write &lt;code&gt;import json&lt;/code&gt; and call &lt;code&gt;json.dumps(...)&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="nx"&gt;AttributeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;module&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="nx"&gt;has&lt;/span&gt; &lt;span class="nx"&gt;no&lt;/span&gt; &lt;span class="nx"&gt;attribute&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dumps&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's importing &lt;em&gt;your&lt;/em&gt; file, not Python's. Don't name files &lt;code&gt;json.py&lt;/code&gt;, &lt;code&gt;random.py&lt;/code&gt;, &lt;code&gt;test.py&lt;/code&gt;, &lt;code&gt;email.py&lt;/code&gt;, and so on. If you ever see this kind of error and the code looks right, check your filenames.&lt;/p&gt;

&lt;h2&gt;
  
  
  Packages: a folder of modules
&lt;/h2&gt;

&lt;p&gt;When a project grows, you group modules into a folder. A folder of modules that can be imported is called a &lt;strong&gt;package&lt;/strong&gt;. A tidy layout looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;textkit_project/
├── pyproject.toml
├── README.md
├── src/
│   └── textkit/
│       ├── __init__.py
│       ├── counting.py
│       └── cli.py
└── tests/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few notes on that layout:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;__init__.py&lt;/code&gt; marks the folder as a package, and it runs when the package is imported. It's often short, and just re-exports the main names so users can write &lt;code&gt;from textkit import count_words&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Putting the package inside &lt;code&gt;src/&lt;/code&gt; is a common choice. It stops you from accidentally importing the local folder instead of the properly installed package, which hides bugs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;tests/&lt;/code&gt; is where your tests go. That's Post 13.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Inside a package, import from your own modules using the full name, like &lt;code&gt;from textkit.counting import top_words&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem environments solve
&lt;/h2&gt;

&lt;p&gt;Now the bigger issue. When you run &lt;code&gt;pip install something&lt;/code&gt;, it puts the library into the Python you're using. Do that for every project, and soon:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Project A needs version 1 of a library, and Project B needs version 2. You can only have one installed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can't tell which libraries each project really needs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You break something on your computer's own Python that other programs depend on.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one is real. On many Linux systems and some Macs, installing into the system Python gets you an "externally managed environment" error. Python is protecting itself. Don't fight it with a scary flag. Use a virtual environment, which is the proper fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  Virtual environments: one box per project
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;virtual environment&lt;/strong&gt; is a private folder holding its own Python and its own installed libraries. Each project gets one, so they never interfere.&lt;/p&gt;

&lt;p&gt;Create one inside your project folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That makes a folder called &lt;code&gt;.venv&lt;/code&gt;. Now &lt;strong&gt;activate&lt;/strong&gt; it, which tells your terminal to use that Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# macOS and Linux&lt;/span&gt;
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate

&lt;span class="c"&gt;# Windows (PowerShell)&lt;/span&gt;
.venv&lt;span class="se"&gt;\S&lt;/span&gt;cripts&lt;span class="se"&gt;\A&lt;/span&gt;ctivate.ps1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your prompt will usually change to show &lt;code&gt;(.venv)&lt;/code&gt;. You can double-check which Python you're using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;which python       &lt;span class="c"&gt;# macOS and Linux (on Windows: where python)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It should point inside &lt;code&gt;.venv&lt;/code&gt;. Now anything you install goes only into this box:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;pydantic
pip list
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you're done, type &lt;code&gt;deactivate&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;A few things that help:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Reproduce an environment.&lt;/strong&gt; &lt;code&gt;pip freeze &amp;gt; requirements.txt&lt;/code&gt; saves a list of what's installed, with versions. Someone else (or you, next year) can rebuild it with &lt;code&gt;pip install -r requirements.txt&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Never commit&lt;/strong&gt; &lt;code&gt;.venv&lt;/code&gt; &lt;strong&gt;to Git.&lt;/strong&gt; It's big and it's specific to your machine. Add &lt;code&gt;.venv/&lt;/code&gt; to your &lt;code&gt;.gitignore&lt;/code&gt; file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tell your editor.&lt;/strong&gt; In VS Code, open the command palette and choose "Python: Select Interpreter," then pick the one inside &lt;code&gt;.venv&lt;/code&gt;. Otherwise your editor will underline imports as missing even though they work.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Wrong environment is the most common cause of&lt;/strong&gt; &lt;code&gt;ModuleNotFoundError&lt;/code&gt;&lt;strong&gt;.&lt;/strong&gt; You installed the library in one place and ran your code in another. Check which Python is active before anything else.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  uv: the modern way
&lt;/h2&gt;

&lt;p&gt;The tools above work, and you should understand them. But there's a newer tool called &lt;strong&gt;uv&lt;/strong&gt; that does the same jobs far faster and in one place: it creates environments, installs libraries, manages Python versions, and keeps a lock file. Many Python projects use it now, and I'd suggest you do too.&lt;/p&gt;

&lt;p&gt;You can install it with &lt;code&gt;pip install uv&lt;/code&gt;, or use the standalone installer from its official site (docs.astral.sh/uv). Tools in this area change quickly, so if a command here doesn't match what you see, check the official docs.&lt;/p&gt;

&lt;p&gt;Starting a new project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv init my_app
&lt;span class="nb"&gt;cd &lt;/span&gt;my_app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That creates a small project: a &lt;code&gt;pyproject.toml&lt;/code&gt;, a &lt;code&gt;README.md&lt;/code&gt;, a &lt;code&gt;main.py&lt;/code&gt;, a &lt;code&gt;.python-version&lt;/code&gt; file that says which Python to use, and a &lt;code&gt;.gitignore&lt;/code&gt; that already excludes &lt;code&gt;.venv&lt;/code&gt;. (The exact files vary a little between versions. It also sets up a Git repository for you.)&lt;/p&gt;

&lt;p&gt;Adding a library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv add pydantic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That does three things at once: it creates the virtual environment if there isn't one, installs the library, and records it in &lt;code&gt;pyproject.toml&lt;/code&gt;. It also writes a &lt;code&gt;uv.lock&lt;/code&gt; file that pins the exact versions of everything. Run your code with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run main.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;uv run&lt;/code&gt; always uses the project's environment, so you never have to remember to activate anything. If a friend clones your project, one command, &lt;code&gt;uv sync&lt;/code&gt;, rebuilds the exact same environment.&lt;/p&gt;

&lt;p&gt;Here's how the two approaches compare:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;th&gt;pip and venv&lt;/th&gt;
&lt;th&gt;uv&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Create an environment&lt;/td&gt;
&lt;td&gt;python -m venv .venv&lt;/td&gt;
&lt;td&gt;automatic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Activate it&lt;/td&gt;
&lt;td&gt;source .venv/bin/activate&lt;/td&gt;
&lt;td&gt;not needed with uv run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Add a library&lt;/td&gt;
&lt;td&gt;pip install name&lt;/td&gt;
&lt;td&gt;uv add name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Record dependencies&lt;/td&gt;
&lt;td&gt;pip freeze &amp;gt; requirements.txt&lt;/td&gt;
&lt;td&gt;automatic in pyproject.toml and uv.lock&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rebuild on another machine&lt;/td&gt;
&lt;td&gt;pip install -r requirements.txt&lt;/td&gt;
&lt;td&gt;uv sync&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Which should you use? Whichever your team or tutorial uses. If you're free to choose, pick uv. This series will show the simplest version of each command, and the ideas are identical either way.&lt;/p&gt;

&lt;h2&gt;
  
  
  pyproject.toml: the project's ID card
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;pyproject.toml&lt;/code&gt; is a single file that describes your project. Nearly every modern Python tool reads it. Here's the one for the small project we'll build below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[project]&lt;/span&gt;
&lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"textkit"&lt;/span&gt;
&lt;span class="py"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"0.1.0"&lt;/span&gt;
&lt;span class="py"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Small tools for counting words in text"&lt;/span&gt;
&lt;span class="py"&gt;readme&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"README.md"&lt;/span&gt;
&lt;span class="py"&gt;requires-python&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="py"&gt;"&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;3.11&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="py"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;

&lt;span class="nn"&gt;[project.optional-dependencies]&lt;/span&gt;
&lt;span class="py"&gt;dev&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="py"&gt;["pytest&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;8.0&lt;/span&gt;&lt;span class="s"&gt;"]&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;
&lt;span class="nn"&gt;[project.scripts]&lt;/span&gt;
&lt;span class="py"&gt;textkit&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"textkit.cli:main"&lt;/span&gt;

&lt;span class="nn"&gt;[build-system]&lt;/span&gt;
&lt;span class="py"&gt;requires&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="py"&gt;["setuptools&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;68&lt;/span&gt;&lt;span class="s"&gt;"]&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="py"&gt;build-backend&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"setuptools.build_meta"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The format is called TOML. It's plain text with sections in square brackets. What each part does:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;[project]&lt;/code&gt; has the basics: the name, the version, a description, and the oldest Python it supports. &lt;code&gt;dependencies&lt;/code&gt; lists the libraries your project needs to run. Ours is empty because it only uses Python itself.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;[project.optional-dependencies]&lt;/code&gt; lists extras. Here, &lt;code&gt;pytest&lt;/code&gt; is only needed by people working on the project, not by people using it, so it goes in a &lt;code&gt;dev&lt;/code&gt; group.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;[project.scripts]&lt;/code&gt; turns a function into a command. The line says: create a command called &lt;code&gt;textkit&lt;/code&gt; that runs the &lt;code&gt;main&lt;/code&gt; function in &lt;code&gt;textkit/cli.py&lt;/code&gt;. After installing, you can type &lt;code&gt;textkit&lt;/code&gt; in your terminal.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;[build-system]&lt;/code&gt; says which tool turns your folder into an installable package. setuptools is the long-standing choice. Others, like hatchling, work too, and you'll see them in plenty of projects. You rarely need to care which.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Many other tools, such as test runners and code formatters, also read settings from the same file, which keeps your project in one place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Versions and lock files
&lt;/h2&gt;

&lt;p&gt;Dependencies usually come with a version rule:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Written as&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;pydantic&amp;gt;=2.0&lt;/td&gt;
&lt;td&gt;Version 2.0 or newer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pydantic==2.5.1&lt;/td&gt;
&lt;td&gt;Exactly this version&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pydantic~=2.5&lt;/td&gt;
&lt;td&gt;2.5 or newer, but below 3.0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;pydantic&amp;gt;=2.0,&amp;lt;3&lt;/td&gt;
&lt;td&gt;At least 2.0, but below 3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Loose rules in &lt;code&gt;pyproject.toml&lt;/code&gt; say what your project &lt;em&gt;can&lt;/em&gt; work with. A &lt;strong&gt;lock file&lt;/strong&gt; (&lt;code&gt;uv.lock&lt;/code&gt;, or a pinned &lt;code&gt;requirements.txt&lt;/code&gt;) records the &lt;em&gt;exact&lt;/em&gt; versions you actually tested, including libraries your libraries depend on. Together they give you flexibility on paper and reliability in practice.&lt;/p&gt;

&lt;p&gt;A quick word on trust. Installing a library runs other people's code on your computer. Before you install something, check that you've spelled the name right (fake packages with near-identical names do exist), and that it's a real, maintained project. It takes ten seconds and avoids a lot of trouble.&lt;/p&gt;

&lt;h2&gt;
  
  
  Another kind of environment: environment variables
&lt;/h2&gt;

&lt;p&gt;AI work adds one more thing. You'll use API keys, which are like passwords for model services. They must never appear in your code or in Git.&lt;/p&gt;

&lt;p&gt;The standard solution is an &lt;strong&gt;environment variable&lt;/strong&gt;, a setting that lives outside your code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MODEL_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Set the MODEL_API_KEY environment variable first&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For local work, people keep their variables in a file called &lt;code&gt;.env&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="py"&gt;MODEL_API_KEY&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="err"&gt;your-key-goes-here&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then they load it at startup with a library like &lt;code&gt;python-dotenv&lt;/code&gt; (&lt;code&gt;load_dotenv()&lt;/code&gt;). The rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Add &lt;code&gt;.env&lt;/code&gt; to &lt;code&gt;.gitignore&lt;/code&gt; so it never gets uploaded.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Commit a &lt;code&gt;.env.example&lt;/code&gt; file with the names but fake values, so others know what to set.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If a key ever &lt;em&gt;does&lt;/em&gt; leak, treat it as stolen: revoke it and make a new one.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Mini project: your first package
&lt;/h2&gt;

&lt;p&gt;Let's turn the word counter from Post 5 into a real package you can install, with its own command. Build the folder layout shown earlier. First, &lt;code&gt;src/textkit/counting.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;count_words&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Count how often each word appears in a piece of text.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;counts&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;top_words&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]]:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Return the n most common words with their counts.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;counts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;count_words&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ranked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ranked&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then &lt;code&gt;src/textkit/__init__.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;textkit.counting&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;count_words&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;top_words&lt;/span&gt;

&lt;span class="n"&gt;__all__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;count_words&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;top_words&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And &lt;code&gt;src/textkit/cli.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;textkit.counting&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;top_words&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:])&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;top_words&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the &lt;code&gt;pyproject.toml&lt;/code&gt; from above, and a &lt;code&gt;README.md&lt;/code&gt; containing a title. Now create an environment and install your package in &lt;strong&gt;editable&lt;/strong&gt; mode, which means changes to your code take effect immediately without reinstalling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; venv .venv
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate        &lt;span class="c"&gt;# on Windows, use the activate line from earlier&lt;/span&gt;
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(With uv, the equivalent is &lt;code&gt;uv pip install -e .&lt;/code&gt; inside a project, or you can structure it as a uv project from the start with &lt;code&gt;uv init --lib&lt;/code&gt;.)&lt;/p&gt;

&lt;p&gt;Now try it from any folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s2"&gt;"import textkit; print(textkit.top_words('The cat sat on the mat. The dog sat on the log.', n=3))"&lt;/span&gt;
&lt;span class="c"&gt;# [('the', 4), ('sat', 2), ('on', 2)]&lt;/span&gt;

textkit the &lt;span class="nb"&gt;cat &lt;/span&gt;sat on the mat
&lt;span class="c"&gt;# the: 2&lt;/span&gt;
&lt;span class="c"&gt;# cat: 1&lt;/span&gt;
&lt;span class="c"&gt;# sat: 1&lt;/span&gt;
&lt;span class="c"&gt;# on: 1&lt;/span&gt;
&lt;span class="c"&gt;# mat: 1&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"the dog and the cat"&lt;/span&gt; | textkit
&lt;span class="c"&gt;# the: 2&lt;/span&gt;
&lt;span class="c"&gt;# dog: 1&lt;/span&gt;
&lt;span class="c"&gt;# and: 1&lt;/span&gt;
&lt;span class="c"&gt;# cat: 1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Take a moment to appreciate that. You typed &lt;code&gt;textkit&lt;/code&gt; in a terminal, and your own code ran. It's no longer a script in a folder. It's a package, with a name, a version, and a command.&lt;/p&gt;

&lt;p&gt;Notice, too, that &lt;code&gt;pip list&lt;/code&gt; now shows &lt;code&gt;textkit&lt;/code&gt; with the path of your folder next to it. That's what "editable" means.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make it yours
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Add a &lt;code&gt;clean.py&lt;/code&gt; module with a function that removes punctuation more thoroughly, and use it from &lt;code&gt;counting.py&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add a real dependency, such as &lt;code&gt;rich&lt;/code&gt; for colored output, and see how it shows up in &lt;code&gt;pyproject.toml&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add a &lt;code&gt;--top 10&lt;/code&gt; option to the command. (Look up &lt;code&gt;argparse&lt;/code&gt; in the Python docs.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Repeat the whole thing with uv, and compare the steps.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Practice
&lt;/h2&gt;

&lt;p&gt;About 20 minutes of real effort on each before asking for help.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create a virtual environment, install a library, and confirm it with &lt;code&gt;pip list&lt;/code&gt;. Then delete the environment and rebuild it from a &lt;code&gt;requirements.txt&lt;/code&gt; file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make two environments. Install &lt;code&gt;requests&lt;/code&gt; in only one of them. Show that &lt;code&gt;import requests&lt;/code&gt; works in one and fails in the other.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Break things on purpose: create a file called &lt;code&gt;json.py&lt;/code&gt;, and watch what happens to a script that imports &lt;code&gt;json&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write a module that behaves differently when it's imported than when it's run directly, using &lt;code&gt;if __name__ == "__main__":&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write a &lt;code&gt;.gitignore&lt;/code&gt; for the project above and explain each line in your own words.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Find a small open-source Python project on GitHub, open its &lt;code&gt;pyproject.toml&lt;/code&gt;, and list its dependencies.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then ask an AI tutor to review your answers, and don't let it rewrite them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to take away
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A module is a Python file, and a package is a folder of modules.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;if __name__ == "__main__":&lt;/code&gt; lets a file work both as a script and as an importable module.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Never name your files after standard library modules.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A virtual environment gives each project its own libraries, so projects can't interfere with each other.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;uv does everything pip and venv do, faster and in one tool, but the ideas are the same.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;pyproject.toml&lt;/code&gt; describes your project: its name, Python version, dependencies, and commands.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lock files pin exact versions so every machine gets the same setup.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;API keys belong in environment variables, never in code or Git.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Previous:&lt;/strong&gt; Post 11, &lt;em&gt;Errors, Logging &amp;amp; Debugging&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next in the series:&lt;/strong&gt; Post 13, &lt;em&gt;Testing with pytest&lt;/em&gt;, where we write tests, mock model calls, and stop worrying about breaking things.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Published via &lt;a href="https://zyvop.com/packaging-environments-modules-virtual-environments-and-pyproject-toml-22hsh?utm_source=devto&amp;amp;utm_medium=crosspost&amp;amp;utm_campaign=syndication" rel="noopener noreferrer"&gt;ZyVOP&lt;/a&gt; — Write once in Markdown, auto-backup to GitHub, and syndicate to Dev.to, Medium &amp;amp; Hashnode in 1 click.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aiengineer</category>
      <category>packaging</category>
      <category>python</category>
      <category>uv</category>
    </item>
    <item>
      <title>Decorators &amp; Context Managers: The @ and with You Keep Seeing</title>
      <dc:creator>Pradeep Kumar</dc:creator>
      <pubDate>Wed, 21 Oct 2026 06:46:37 +0000</pubDate>
      <link>https://dev.to/pradeep_kumar_bc4e7e9f7ec/decorators-context-managers-the-and-with-you-keep-seeing-p6h</link>
      <guid>https://dev.to/pradeep_kumar_bc4e7e9f7ec/decorators-context-managers-the-and-with-you-keep-seeing-p6h</guid>
      <description>&lt;p&gt;Open almost any modern Python project and you'll spot two things quickly. Lines that start with &lt;code&gt;@&lt;/code&gt;, like &lt;code&gt;@app.get("/hello")&lt;/code&gt;. And blocks that start with &lt;code&gt;with&lt;/code&gt;, like &lt;code&gt;with torch.no_grad():&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;They look like magic if nobody explains them. They're not. Both are built from ideas you already met: functions as values and closures from Post 6, and generators from Post 7. This post is core, because FastAPI, PyTorch, and plenty of AI libraries lean on both.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem decorators solve
&lt;/h2&gt;

&lt;p&gt;Say you want to know how long a function takes. You could add timing code to it. Then you want the same for five other functions. Then you want to add logging. Soon every function is half timing code and half the thing it was meant to do.&lt;/p&gt;

&lt;p&gt;What you'd like is a way to wrap extra behavior around a function &lt;em&gt;without touching its insides&lt;/em&gt;. That's what a decorator does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building a decorator by hand
&lt;/h2&gt;

&lt;p&gt;A decorator is a function that takes a function and returns a new, improved function. Here's one that measures time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;timed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; took &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; seconds&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrapper&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Take it slowly, because everything in there comes from Post 6:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;timed&lt;/code&gt; takes a function called &lt;code&gt;func&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Inside, it defines &lt;code&gt;wrapper&lt;/code&gt;, a new function that starts a clock, calls the original &lt;code&gt;func&lt;/code&gt;, stops the clock, prints the time, and returns the original result.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;*args&lt;/code&gt; and &lt;code&gt;**kwargs&lt;/code&gt; let the wrapper accept whatever the original function accepts and pass it along.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;wrapper&lt;/code&gt; is a closure. It remembers &lt;code&gt;func&lt;/code&gt; after &lt;code&gt;timed&lt;/code&gt; has finished.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;timed&lt;/code&gt; returns &lt;code&gt;wrapper&lt;/code&gt;, without calling it.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now use it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;slow_add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;

&lt;span class="n"&gt;slow_add&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;timed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slow_add&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;slow_add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;# slow_add took 0.500 seconds
# 5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We replaced &lt;code&gt;slow_add&lt;/code&gt; with the wrapped version. Python has a shortcut for exactly this pattern, and that's the &lt;code&gt;@&lt;/code&gt; symbol:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@timed&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;slow_add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Those two versions do the same thing. &lt;code&gt;@timed&lt;/code&gt; above a function simply means &lt;code&gt;slow_add = timed(slow_add)&lt;/code&gt;. That's all it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  A small fix: functools.wraps
&lt;/h2&gt;

&lt;p&gt;There's one annoying side effect. After decorating, the function forgets who it is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;slow_add&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# wrapper
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's because &lt;code&gt;slow_add&lt;/code&gt; is now really the &lt;code&gt;wrapper&lt;/code&gt; function. It can mess up error messages, logs, and tools that look at function names. The standard fix is to add &lt;code&gt;@wraps(func)&lt;/code&gt; to the wrapper:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;functools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;wraps&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;timed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nd"&gt;@wraps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; took &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; seconds&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrapper&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now &lt;code&gt;slow_add.__name__&lt;/code&gt; correctly says &lt;code&gt;slow_add&lt;/code&gt;. Treat &lt;code&gt;@wraps&lt;/code&gt; as a habit you always include. It's a decorator that's used inside a decorator, which is a fun thing to notice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decorators that take settings
&lt;/h2&gt;

&lt;p&gt;Often you want to customize a decorator, like &lt;code&gt;@retry(attempts=5)&lt;/code&gt;. That needs one more layer, and this is the part that confuses everyone at first. Here's the retry helper from Post 6, rewritten as a decorator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attempts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;decorator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nd"&gt;@wraps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wrapper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
                &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Attempt &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                        &lt;span class="k"&gt;raise&lt;/span&gt;
                    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrapper&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;decorator&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three layers, each with one job:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;retry(...)&lt;/code&gt; takes the settings and returns a decorator.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;decorator(func)&lt;/code&gt; takes the function and returns the wrapper.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;wrapper(...)&lt;/code&gt; is what actually runs each time you call the function.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So &lt;code&gt;@retry(attempts=5)&lt;/code&gt; first calls &lt;code&gt;retry(attempts=5)&lt;/code&gt;, which hands back a decorator, and then that decorator is applied to the function below it. If three layers feels like a lot, it is. Read it twice, then type it out yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decorators that register instead of wrap
&lt;/h2&gt;

&lt;p&gt;Not every decorator changes the function. Some just make a note of it and hand it back unchanged. This is how web frameworks find your route handlers, and it's how many AI frameworks keep track of the tools a model is allowed to call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;func&lt;/span&gt;

&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_weather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sunny in &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;city&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get_weather&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Lisbon&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;    &lt;span class="c1"&gt;# Sunny in Lisbon
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;                       &lt;span class="c1"&gt;# ['get_weather']
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember the dictionary of functions from Post 6? This is the tidy version. Mark a function with &lt;code&gt;@tool&lt;/code&gt; and it lands in the registry. When a model asks for &lt;code&gt;get_weather&lt;/code&gt; by name, you look it up and run it.&lt;/p&gt;

&lt;p&gt;When you see &lt;code&gt;@app.get("/hello")&lt;/code&gt; in FastAPI, it's the same family of idea: it tells the framework "when someone visits &lt;code&gt;/hello&lt;/code&gt;, run this function."&lt;/p&gt;

&lt;h2&gt;
  
  
  A few built-in decorators worth knowing
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;@cache&lt;/code&gt; (from &lt;code&gt;functools&lt;/code&gt;) remembers results, so calling a function again with the same input returns instantly:
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;functools&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cache&lt;/span&gt;

&lt;span class="nd"&gt;@cache&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;expensive_lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Computing for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;expensive_lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;    &lt;span class="c1"&gt;# Computing for hello...  then 50
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;expensive_lookup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;    &lt;span class="c1"&gt;# 50  (no computing this time)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two warnings. The inputs must be things that can be dictionary keys, so passing a list fails (remember Post 5). And don't cache anything where you &lt;em&gt;want&lt;/em&gt; a fresh answer each time, like most model calls.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;@staticmethod&lt;/code&gt;, &lt;code&gt;@property&lt;/code&gt;, and &lt;code&gt;@dataclass&lt;/code&gt; show up when we cover classes in the next post.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Context managers: cleaning up after yourself
&lt;/h2&gt;

&lt;p&gt;Now the &lt;code&gt;with&lt;/code&gt; statement. Some things need to be opened and then closed properly: files, network connections, database sessions, locks. If you forget to close them, or your program crashes in the middle, you get leaks and strange bugs.&lt;/p&gt;

&lt;p&gt;You've already seen the fix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;demo.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;closed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# True
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When the &lt;code&gt;with&lt;/code&gt; block ends, the file is closed. Always. Even if an error happens inside the block. That guarantee is the whole point.&lt;/p&gt;

&lt;p&gt;You can open more than one thing in the same line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;demo.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;copy.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How &lt;code&gt;with&lt;/code&gt; works
&lt;/h2&gt;

&lt;p&gt;Anything that works with &lt;code&gt;with&lt;/code&gt; is called a context manager. It's an object with two special methods: one that runs when you enter the block, and one that runs when you leave, no matter how you leave. Here's a timer built that way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Timer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__enter__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__exit__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc_value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;traceback&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Took &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; seconds&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Timer&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Took 0.30 seconds
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We haven't covered classes yet (that's Post 9), so just read this as a recipe for now. &lt;code&gt;__enter__&lt;/code&gt; is the setup. &lt;code&gt;__exit__&lt;/code&gt; is the cleanup. If an error happened inside the block, the three extra arguments describe it, and returning &lt;code&gt;False&lt;/code&gt; means "let the error carry on."&lt;/p&gt;

&lt;h2&gt;
  
  
  The easy way: &lt;code&gt;@contextmanager&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Writing a whole class for a small job is a bit much. Python has a shortcut that uses a generator, which you met in Post 7. Everything before &lt;code&gt;yield&lt;/code&gt; is setup, and everything after is cleanup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;contextlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;contextmanager&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="nd"&gt;@contextmanager&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt;
    &lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;label&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; took &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; seconds&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sleeping&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# sleeping took 0.30 seconds
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;try&lt;/code&gt; and &lt;code&gt;finally&lt;/code&gt; matter. Code in &lt;code&gt;finally&lt;/code&gt; always runs, even if the block inside &lt;code&gt;with&lt;/code&gt; raises an error. So the timer still reports in a crash:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;crashing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;oops&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# crashing took 0.00 seconds
# then the ValueError appears as usual
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice how this ties together. &lt;code&gt;@contextmanager&lt;/code&gt; is a decorator, wrapped around a generator, producing something you use with &lt;code&gt;with&lt;/code&gt;. If that sentence made sense, you've understood the last three posts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where you'll see these in AI work
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;with torch.no_grad():&lt;/code&gt; switches off gradient tracking while you run a model for predictions, then switches it back on afterward. You'll write it often once we reach PyTorch. (It can also be used as a decorator, so you'll see both styles.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Network and database sessions&lt;/strong&gt; that must close cleanly.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Temporary settings&lt;/strong&gt;, like running a block of code with a different configuration and then restoring the original.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Timing and tracing&lt;/strong&gt; blocks of code, like our &lt;code&gt;timer&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Route handlers and tools&lt;/strong&gt; registered with decorators in web and agent frameworks.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Mini project: a small toolkit
&lt;/h2&gt;

&lt;p&gt;Let's combine everything in one file. Create &lt;code&gt;toolkit.py&lt;/code&gt; with the &lt;code&gt;timed&lt;/code&gt; decorator, the &lt;code&gt;retry&lt;/code&gt; decorator, and the &lt;code&gt;timer&lt;/code&gt; context manager from above, then add this at the bottom:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;

&lt;span class="nd"&gt;@timed&lt;/span&gt;
&lt;span class="nd"&gt;@retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attempts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fake_model_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Model busy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Answer to: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;timer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;whole batch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What is a decorator?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What is a context manager?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;fake_model_call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it a few times. The failures are random, so every run looks a little different. A run with one failed attempt might print:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Attempt 1 failed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Model busy&lt;/span&gt;
&lt;span class="s"&gt;fake_model_call took 0.500 seconds&lt;/span&gt;
&lt;span class="na"&gt;Answer to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;What is a decorator?&lt;/span&gt;
&lt;span class="s"&gt;fake_model_call took 0.200 seconds&lt;/span&gt;
&lt;span class="na"&gt;Answer to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;What is a context manager?&lt;/span&gt;
&lt;span class="s"&gt;whole batch took 0.70 seconds&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A detail worth noticing: when you stack decorators, the one closest to the function is applied first. So &lt;code&gt;@retry&lt;/code&gt; wraps the function, and then &lt;code&gt;@timed&lt;/code&gt; wraps &lt;em&gt;that&lt;/em&gt;. That means the time you see includes the retries and the waiting between them. If you swapped the two lines, &lt;code&gt;timed&lt;/code&gt; would report only a single attempt each time.&lt;/p&gt;

&lt;p&gt;You've just written the core of a tool that real AI apps need: a function that measures itself, tries again when it fails, and sits inside a timed block. And none of that code is inside the function that makes the model call.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make it yours
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Add a &lt;code&gt;@log_calls&lt;/code&gt; decorator that prints the function name and its arguments before each call.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write your own simple cache using a dictionary, then compare it with &lt;code&gt;@cache&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write a context manager that temporarily changes a value in a dictionary and puts the original back afterward.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add a &lt;code&gt;max_total_seconds&lt;/code&gt; setting to &lt;code&gt;retry&lt;/code&gt; so it gives up if too much time has passed.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Practice
&lt;/h2&gt;

&lt;p&gt;About 20 minutes of real effort on each before asking for help.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Write a &lt;code&gt;@shout&lt;/code&gt; decorator that makes any function's returned text uppercase.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write a &lt;code&gt;@count_calls&lt;/code&gt; decorator that keeps track of how many times a function has been called. (Hint: you can store the count on the wrapper itself, like &lt;code&gt;wrapper.calls = 0&lt;/code&gt;.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write a context manager with &lt;code&gt;@contextmanager&lt;/code&gt; that prints "Entering" when the block starts and "Leaving" when it ends, even if there's an error.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use &lt;code&gt;@cache&lt;/code&gt; on a recursive Fibonacci function and compare how long &lt;code&gt;fib(35)&lt;/code&gt; takes with and without it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Without using &lt;code&gt;@&lt;/code&gt;, write the exact equivalent of decorating a function with &lt;code&gt;@timed&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then ask an AI tutor to review your code, not rewrite it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to take away
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A decorator is a function that takes a function and returns a new one. &lt;code&gt;@name&lt;/code&gt; is just a shortcut for &lt;code&gt;func = name(func)&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use &lt;code&gt;@wraps(func)&lt;/code&gt; inside every decorator you write.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A decorator with settings has three layers: settings, function, wrapper.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Some decorators wrap, and some simply register a function and return it unchanged.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A context manager guarantees setup and cleanup around a block of code, even when something goes wrong.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;@contextmanager&lt;/code&gt; lets you build one with a generator and a &lt;code&gt;try&lt;/code&gt;/&lt;code&gt;finally&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Previous:&lt;/strong&gt; Post 7, &lt;em&gt;Iterators, Generators &amp;amp; Comprehensions&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next in the series:&lt;/strong&gt; Post 9, &lt;em&gt;OOP the Practical Way&lt;/em&gt;, where we cover classes, dataclasses, and when to use composition instead of inheritance.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Published via &lt;a href="https://zyvop.com/decorators-context-managers-the-and-with-you-keep-seeing-c925u?utm_source=devto&amp;amp;utm_medium=crosspost&amp;amp;utm_campaign=syndication" rel="noopener noreferrer"&gt;ZyVOP&lt;/a&gt; — Write once in Markdown, auto-backup to GitHub, and syndicate to Dev.to, Medium &amp;amp; Hashnode in 1 click.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aiengineer</category>
      <category>contextmanagers</category>
      <category>decorators</category>
      <category>programming</category>
    </item>
    <item>
      <title>Iterators, Generators &amp; Comprehensions: How Python Handles Streams of Data</title>
      <dc:creator>Pradeep Kumar</dc:creator>
      <pubDate>Wed, 21 Oct 2026 06:45:33 +0000</pubDate>
      <link>https://dev.to/pradeep_kumar_bc4e7e9f7ec/iterators-generators-comprehensions-how-python-handles-streams-of-data-mam</link>
      <guid>https://dev.to/pradeep_kumar_bc4e7e9f7ec/iterators-generators-comprehensions-how-python-handles-streams-of-data-mam</guid>
      <description>&lt;p&gt;When you chat with an AI model and the answer appears word by word, instead of all at once, something specific is happening in the code. The text is arriving in small pieces, and your program is handling each piece as it shows up.&lt;/p&gt;

&lt;p&gt;The Python feature behind that is called a generator. This post explains generators, along with the ideas they're built on: comprehensions and iterators. They're core, so I'd read them all. You'll use them for reading big datasets, building pipelines, batching requests, and streaming model output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comprehensions: loops in one line
&lt;/h2&gt;

&lt;p&gt;Here's a loop you've probably written something like before. It builds a list of squares:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;squares&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;squares&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;strong&gt;list comprehension&lt;/strong&gt; does the same job in one line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;squares&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;squares&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# [1, 4, 9, 16, 25, 36]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read it like English: "give me &lt;code&gt;n * n&lt;/code&gt; for each &lt;code&gt;n&lt;/code&gt; in &lt;code&gt;numbers&lt;/code&gt;."&lt;/p&gt;

&lt;p&gt;You can add a filter at the end:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;evens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;evens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# [2, 4, 6]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And combine the two:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;banana&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fig&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cherry&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kiwi&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="c1"&gt;# ['BANANA', 'CHERRY', 'KIWI']
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remember &lt;code&gt;map&lt;/code&gt; and &lt;code&gt;filter&lt;/code&gt; from the last post? Comprehensions do the same things, and most Python programmers find them easier to read. You'll see them far more often.&lt;/p&gt;

&lt;p&gt;The same idea works for dictionaries and sets, just with different brackets:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="c1"&gt;# {'banana': 6, 'fig': 3, 'cherry': 6, 'kiwi': 4}
&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="c1"&gt;# {3, 4, 6}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can even loop over a list of lists in one go. Read it left to right, like two loops stacked together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;grid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;grid&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="c1"&gt;# [1, 2, 3, 4, 5]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One piece of advice: comprehensions are for simple jobs. If you find yourself squeezing in several conditions and nested logic, a normal loop is kinder to whoever reads it next, and that person is often you in three months.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a for loop really does
&lt;/h2&gt;

&lt;p&gt;Let's look under the hood of &lt;code&gt;for&lt;/code&gt;. Two words matter here.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;An &lt;strong&gt;iterable&lt;/strong&gt; is anything you can loop over: a list, a string, a dictionary, a file.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;An &lt;strong&gt;iterator&lt;/strong&gt; is the object that actually hands out the items one at a time and remembers where it is.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can see this by hand with &lt;code&gt;iter()&lt;/code&gt; and &lt;code&gt;next()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;letters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;c&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;iter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;letters&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;    &lt;span class="c1"&gt;# a
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;    &lt;span class="c1"&gt;# b
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;    &lt;span class="c1"&gt;# c
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;    &lt;span class="c1"&gt;# StopIteration error
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When there's nothing left, Python raises a &lt;code&gt;StopIteration&lt;/code&gt; signal. A &lt;code&gt;for&lt;/code&gt; loop is just a tidy wrapper around exactly this: it asks for an iterator, keeps calling &lt;code&gt;next&lt;/code&gt;, and quietly stops when it sees &lt;code&gt;StopIteration&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's the part that catches people out. An iterator gets used up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;iter&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;    &lt;span class="c1"&gt;# [1, 2, 3]
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;it&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;    &lt;span class="c1"&gt;# []
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second time, there's nothing left, so you get an empty list. Keep that in mind, because generators behave the same way.&lt;/p&gt;

&lt;p&gt;Two built-in helpers you'll use all the time. &lt;code&gt;enumerate&lt;/code&gt; gives you a counter along with each item, and &lt;code&gt;zip&lt;/code&gt; walks through two lists side by side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 1 a
# 2 b
&lt;/span&gt;
&lt;span class="n"&gt;questions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;q1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;q2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;q3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;answers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;questions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# q1 a1
# q2 a2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that &lt;code&gt;zip&lt;/code&gt; stops at the shorter list. The third question quietly disappears. That's useful to know when you pair up questions and answers in an evaluation set.&lt;/p&gt;

&lt;h2&gt;
  
  
  Generators: functions that pause
&lt;/h2&gt;

&lt;p&gt;Now the main event. A normal function runs from top to bottom and returns once. A &lt;strong&gt;generator&lt;/strong&gt; function can hand back a value, pause right there, and pick up where it left off the next time you ask. The magic word is &lt;code&gt;yield&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;count_up&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
        &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;count_up&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 1
# 2
# 3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of &lt;code&gt;return&lt;/code&gt;, it uses &lt;code&gt;yield&lt;/code&gt;. Each time the loop asks for a value, the function runs until it hits &lt;code&gt;yield&lt;/code&gt;, hands the value over, and freezes. When the loop asks again, it wakes up on the line after &lt;code&gt;yield&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;To see how lazy that really is, add some prints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;noisy&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;starting&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;middle&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;done&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;gen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;noisy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;created&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gen&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;created
starting
1
middle
2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Look at the order. Creating the generator printed nothing. The function body doesn't start until the first &lt;code&gt;next()&lt;/code&gt;. And "done" hasn't shown up yet, because we never asked for a third value. Generators do work only when you ask, and only as much as you ask for.&lt;/p&gt;

&lt;p&gt;Just like iterators, a generator is used up after one pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why lazy matters: memory
&lt;/h2&gt;

&lt;p&gt;Here's the real payoff. Compare building a million squares as a list with a generator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;

&lt;span class="n"&gt;big_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1_000_000&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="n"&gt;big_gen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1_000_000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getsizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;big_list&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;    &lt;span class="c1"&gt;# about 8,000,000 bytes
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getsizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;big_gen&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;     &lt;span class="c1"&gt;# about 200 bytes
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The only difference in the code is the brackets. Square brackets build the whole list in memory right now. Round brackets make a &lt;strong&gt;generator expression&lt;/strong&gt;, which holds almost nothing and produces each value when asked. (The list's size doesn't even count the numbers inside it, so the real gap is bigger.)&lt;/p&gt;

&lt;p&gt;A million numbers is no problem for a modern computer. But replace that with a dataset of 50 million text records and it's a different story. With a generator, your program handles one record at a time, and memory stays small whether the file has a thousand lines or a billion.&lt;/p&gt;

&lt;p&gt;Functions like &lt;code&gt;sum&lt;/code&gt;, &lt;code&gt;max&lt;/code&gt;, and &lt;code&gt;any&lt;/code&gt; accept generator expressions directly, no extra brackets needed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;    &lt;span class="c1"&gt;# 285
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Reading big files, one line at a time
&lt;/h2&gt;

&lt;p&gt;Files are lazy too. Looping over a file gives you one line at a time, without loading the whole thing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_lines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(The &lt;code&gt;with&lt;/code&gt; line makes sure the file gets closed properly. We'll cover that in the next post.)&lt;/p&gt;

&lt;p&gt;Now you can chain generators together into a &lt;strong&gt;pipeline&lt;/strong&gt;, where each step takes one item, does something, and passes it on:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;keep_real_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;min_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;min_length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Neither step ever holds the whole dataset. One line flows through at a time. This is how people clean huge text collections for AI training and search without needing a monster computer.&lt;/p&gt;

&lt;h2&gt;
  
  
  Streaming model output
&lt;/h2&gt;

&lt;p&gt;Now back to where we started. When you ask a model to stream its reply, the answer arrives in small chunks as it's produced. Many model libraries hand you those chunks as something you can loop over, which means a generator or an iterator under the hood.&lt;/p&gt;

&lt;p&gt;You can fake the whole thing to feel how it works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fake_model_stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;fake_model_stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Generators make streaming feel natural&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it and the sentence appears one word at a time. Two small details: &lt;code&gt;end=""&lt;/code&gt; stops &lt;code&gt;print&lt;/code&gt; from adding a new line after each chunk, and &lt;code&gt;flush=True&lt;/code&gt; makes the text show on screen immediately instead of waiting.&lt;/p&gt;

&lt;p&gt;When we build our first LLM app in Post 49, the code that handles a streamed reply will look almost exactly like this loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  Batching: a pattern you'll use constantly
&lt;/h2&gt;

&lt;p&gt;AI services often work better, and cheaper, when you send items in groups instead of one by one. Embedding thousands of documents is a classic case. So you need a way to chop a stream of items into batches.&lt;/p&gt;

&lt;p&gt;For a list, it's quick:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;batches_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;batch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;batches_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# [0, 1, 2, 3]
# [4, 5, 6, 7]
# [8, 9]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But that needs the whole list in memory. In the mini project below, we'll write a version that works on any stream.&lt;/p&gt;

&lt;p&gt;(If you're on Python 3.12 or newer, there's also &lt;code&gt;itertools.batched&lt;/code&gt;, which does this for you. It's good to know, and it's even better to have written one yourself first.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Mini project: a lazy text pipeline
&lt;/h2&gt;

&lt;p&gt;Let's put it all together. We'll read a file line by line, throw away blank and very short lines, and group what's left into batches of three, like you would before sending text to an embedding service. We won't call any real service. We'll just count.&lt;/p&gt;

&lt;p&gt;First, create a file called &lt;code&gt;notes.txt&lt;/code&gt; with this content (include the blank line):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;Python&lt;/span&gt; &lt;span class="n"&gt;generators&lt;/span&gt; &lt;span class="k"&gt;are&lt;/span&gt; &lt;span class="n"&gt;lazy&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;save&lt;/span&gt; &lt;span class="n"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;

&lt;span class="n"&gt;ok&lt;/span&gt;
&lt;span class="n"&gt;A&lt;/span&gt; &lt;span class="n"&gt;batch&lt;/span&gt; &lt;span class="n"&gt;groups&lt;/span&gt; &lt;span class="n"&gt;several&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="n"&gt;together&lt;/span&gt; &lt;span class="k"&gt;before&lt;/span&gt; &lt;span class="n"&gt;processing&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;Streaming&lt;/span&gt; &lt;span class="n"&gt;sends&lt;/span&gt; &lt;span class="k"&gt;output&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;small&lt;/span&gt; &lt;span class="n"&gt;chunks&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="n"&gt;made&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;short&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;
&lt;span class="n"&gt;Comprehensions&lt;/span&gt; &lt;span class="k"&gt;are&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;compact&lt;/span&gt; &lt;span class="n"&gt;way&lt;/span&gt; &lt;span class="k"&gt;to&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;Iterators&lt;/span&gt; &lt;span class="n"&gt;remember&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;they&lt;/span&gt; &lt;span class="k"&gt;are&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;sequence&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;Decorators&lt;/span&gt; &lt;span class="n"&gt;come&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="k"&gt;next&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="n"&gt;this&lt;/span&gt; &lt;span class="n"&gt;series&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then create &lt;code&gt;pipeline.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_lines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;keep_real_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;min_length&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;min_length&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;batches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;batch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;batch&lt;/span&gt;
            &lt;span class="n"&gt;batch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;batch&lt;/span&gt;

&lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;read_lines&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;notes.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;good_lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;keep_real_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;batch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;batches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;good_lines&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Batch &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;batch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; lines&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Batch 1&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3 lines&lt;/span&gt;
&lt;span class="na"&gt;Batch 2&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;3 lines&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's the thing worth noticing. The two lines that create &lt;code&gt;lines&lt;/code&gt; and &lt;code&gt;good_lines&lt;/code&gt; do &lt;em&gt;no work at all&lt;/em&gt;. They just set up the pipeline. The reading, filtering, and batching only happen once the &lt;code&gt;for&lt;/code&gt; loop at the bottom starts asking for values. And at any moment, only a few lines are in memory.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;batches&lt;/code&gt; function collects items into a small list, yields it when it's full, and then yields whatever is left over at the end. That last &lt;code&gt;if batch:&lt;/code&gt; check is easy to forget, and without it you'd lose the final partial batch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make it yours
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Print the first 40 characters of each line in a batch.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Replace the &lt;code&gt;print&lt;/code&gt; with a fake &lt;code&gt;embed(batch)&lt;/code&gt; function that returns the length of each line.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use &lt;code&gt;itertools.islice&lt;/code&gt; to process only the first 100 lines.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Generate a file with a million lines and run the pipeline on it. Then change &lt;code&gt;read_lines&lt;/code&gt; to return a list instead, and compare how the memory use feels.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Practice
&lt;/h2&gt;

&lt;p&gt;About 20 minutes of real effort on each.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Use a comprehension to turn a sentence into a list of its word lengths.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use two lists and a dictionary comprehension (with &lt;code&gt;zip&lt;/code&gt;) to build a dictionary from names and ages.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write a generator &lt;code&gt;countdown(n)&lt;/code&gt; that yields &lt;code&gt;n&lt;/code&gt;, then &lt;code&gt;n - 1&lt;/code&gt;, down to 1.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write a generator &lt;code&gt;fibonacci()&lt;/code&gt; that goes on forever, and use a loop with &lt;code&gt;break&lt;/code&gt; to print the first 10 numbers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Change &lt;code&gt;read_lines&lt;/code&gt; so it only yields lines that contain a keyword you pass in.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;After you've tried, ask an AI tutor to review your code, not rewrite it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to take away
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A comprehension builds a list, dictionary, or set in one readable line.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A &lt;code&gt;for&lt;/code&gt; loop asks an iterable for an iterator and calls &lt;code&gt;next&lt;/code&gt; until &lt;code&gt;StopIteration&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Iterators and generators get used up after one pass.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A generator uses &lt;code&gt;yield&lt;/code&gt; to hand back one value at a time and pause in between.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Generators are lazy, so they keep memory small and let you work with data of any size.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Streaming model output, file pipelines, and batching all rely on this idea.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Previous:&lt;/strong&gt; Post 6, &lt;em&gt;Functions in Depth&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next in the series:&lt;/strong&gt; Post 8, &lt;em&gt;Decorators &amp;amp; Context Managers&lt;/em&gt;, which explains the &lt;code&gt;with&lt;/code&gt; statement and the &lt;code&gt;@&lt;/code&gt; lines you'll see all over FastAPI and PyTorch code.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Published via &lt;a href="https://zyvop.com/iterators-generators-comprehensions-how-python-handles-streams-of-data-vzofg?utm_source=devto&amp;amp;utm_medium=crosspost&amp;amp;utm_campaign=syndication" rel="noopener noreferrer"&gt;ZyVOP&lt;/a&gt; — Write once in Markdown, auto-backup to GitHub, and syndicate to Dev.to, Medium &amp;amp; Hashnode in 1 click.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aiengineer</category>
      <category>closures</category>
      <category>functions</category>
      <category>programming</category>
    </item>
    <item>
      <title>Functions in Depth: Scope, Closures, and Passing Functions Around</title>
      <dc:creator>Pradeep Kumar</dc:creator>
      <pubDate>Wed, 21 Oct 2026 06:19:02 +0000</pubDate>
      <link>https://dev.to/pradeep_kumar_bc4e7e9f7ec/functions-in-depth-scope-closures-and-passing-functions-around-2f9n</link>
      <guid>https://dev.to/pradeep_kumar_bc4e7e9f7ec/functions-in-depth-scope-closures-and-passing-functions-around-2f9n</guid>
      <description>&lt;p&gt;In Post 4 you wrote simple functions that take an input and return an output. That's the easy part. This post goes into the parts that confuse people later, and the parts that show up everywhere in AI code.&lt;/p&gt;

&lt;p&gt;This one is core, so I wouldn't skip it, even if you've written Python before. Functions that take other functions as input are all over the place in modern AI work. Callbacks, decorators, and the way a model asks your code to run a tool all depend on the ideas below.&lt;/p&gt;

&lt;h2&gt;
  
  
  A quick recap, plus a trap
&lt;/h2&gt;

&lt;p&gt;You can pass arguments by position or by name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; is &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Asha&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;              &lt;span class="c1"&gt;# Asha is 30
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Asha&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;          &lt;span class="c1"&gt;# Asha is 25
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;41&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ravi&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;# Ravi is 41
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Passing by name is clearer, and you'll do it a lot with AI libraries, where a call might have a dozen options.&lt;/p&gt;

&lt;p&gt;Now the trap. Look at this function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt;
    &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;add_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;    &lt;span class="c1"&gt;# ['a']
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;add_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;    &lt;span class="c1"&gt;# ['a', 'b']   &amp;lt;- wait, what?
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The second call should have returned &lt;code&gt;['b']&lt;/code&gt;. It didn't, because Python makes the default list &lt;em&gt;once&lt;/em&gt;, when the function is defined, and then reuses that same list on every call. Changes pile up.&lt;/p&gt;

&lt;p&gt;The fix is to use &lt;code&gt;None&lt;/code&gt; as the default and make the list inside:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rule of thumb: never use a list or dictionary as a default value.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;*args&lt;/code&gt; and &lt;code&gt;**kwargs&lt;/code&gt;: any number of arguments
&lt;/h2&gt;

&lt;p&gt;Sometimes you don't know how many arguments a function will get. A star in front of a parameter name collects extras.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;total&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;total&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;    &lt;span class="c1"&gt;# 6
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;total&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;           &lt;span class="c1"&gt;# 0
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside the function, &lt;code&gt;numbers&lt;/code&gt; is a tuple holding everything that was passed in.&lt;/p&gt;

&lt;p&gt;Two stars collect named arguments into a dictionary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;x&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# model x
# temperature 0.2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The names are just convention: &lt;code&gt;*args&lt;/code&gt; and &lt;code&gt;**kwargs&lt;/code&gt; are what most people write. The stars are what matter.&lt;/p&gt;

&lt;p&gt;The stars also work in reverse, when you &lt;em&gt;call&lt;/em&gt; a function. They spread out a collection into separate arguments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;total&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;            &lt;span class="c1"&gt;# same as total(1, 2, 3)
&lt;/span&gt;
&lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;y&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;temperature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;                    &lt;span class="c1"&gt;# same as show(model="y", temperature=0.5)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why does this matter for AI work? You'll often write a function that wraps another one and just passes everything along. A wrapper around an API call, for example, shouldn't have to list every option the API supports. It collects them with &lt;code&gt;**kwargs&lt;/code&gt; and hands them over.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scope: where a variable lives
&lt;/h2&gt;

&lt;p&gt;A variable made inside a function belongs to that function only.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;local_only&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;

&lt;span class="nf"&gt;local_only&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# NameError: name 'secret' is not defined
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python looks for names in a set order: first inside the current function (local), then in any function wrapped around it, then at the top of your file (global), and finally in Python's built-in names like &lt;code&gt;len&lt;/code&gt; and &lt;code&gt;print&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now a puzzle that catches nearly everyone once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;increase&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="nf"&gt;increase&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;UnboundLocalError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cannot&lt;/span&gt; &lt;span class="k"&gt;access&lt;/span&gt; &lt;span class="k"&gt;local&lt;/span&gt; &lt;span class="k"&gt;variable&lt;/span&gt; &lt;span class="s1"&gt;'counter'&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;it&lt;/span&gt; &lt;span class="k"&gt;is&lt;/span&gt; &lt;span class="k"&gt;not&lt;/span&gt; &lt;span class="n"&gt;associated&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'd expect that to work, since &lt;code&gt;counter&lt;/code&gt; clearly exists above. The problem is that when you &lt;em&gt;assign&lt;/em&gt; to a name inside a function, Python decides that name is local to the function. So on the right-hand side it looks for a local &lt;code&gt;counter&lt;/code&gt; that doesn't exist yet.&lt;/p&gt;

&lt;p&gt;You can fix it with the &lt;code&gt;global&lt;/code&gt; keyword, but I'd avoid that. Code that secretly changes outside variables gets very hard to follow. The cleaner way is to pass things in and return things out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;increase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;increase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Functions are just values
&lt;/h2&gt;

&lt;p&gt;This is the big idea of the post. In Python, a function is an object like any other. You can put it in a variable, a list, or a dictionary, and pass it to another function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;shout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;whisper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;styles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;shout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;whisper&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;whisper&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shout&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hello&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;      &lt;span class="c1"&gt;# HELLO
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;styles&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;whisper&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HELLO&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;    &lt;span class="c1"&gt;# hello
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice there are no brackets after &lt;code&gt;shout&lt;/code&gt; and &lt;code&gt;whisper&lt;/code&gt; when they go into the dictionary. Writing &lt;code&gt;shout&lt;/code&gt; means "the function itself." Writing &lt;code&gt;shout("hi")&lt;/code&gt; means "run it now."&lt;/p&gt;

&lt;p&gt;That dictionary trick comes back later. When a language model decides it wants to use a tool, it sends back the &lt;em&gt;name&lt;/em&gt; of the tool as text. Your code looks that name up in a dictionary like this one and runs the matching function. That's all tool calling is, at its core.&lt;/p&gt;

&lt;h2&gt;
  
  
  Higher-order functions
&lt;/h2&gt;

&lt;p&gt;A function that takes another function as input, or returns one, is called a higher-order function. It sounds fancy, but you've already used one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;apply_to_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;apply_to_all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;    &lt;span class="c1"&gt;# ['A', 'B']
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python's built-in &lt;code&gt;sorted()&lt;/code&gt; is one too. It takes an optional &lt;code&gt;key&lt;/code&gt; that tells it &lt;em&gt;how&lt;/em&gt; to sort:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;banana&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fig&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cherry&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;    &lt;span class="c1"&gt;# ['fig', 'banana', 'cherry']
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here &lt;code&gt;len&lt;/code&gt; is passed as a function. Python calls it on each word and sorts by the results: 6, 3, 6. That also explains the line from Post 5: &lt;code&gt;sorted(counts, key=counts.get)&lt;/code&gt; sorts the words by looking up each one's count.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lambdas: tiny throwaway functions
&lt;/h2&gt;

&lt;p&gt;Sometimes you need a small function just once, and giving it a name feels like overkill. A &lt;code&gt;lambda&lt;/code&gt; is a function written in one line, with no name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;square&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;square&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;    &lt;span class="c1"&gt;# 16
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The part before the colon is the input, and the part after is the result. Lambdas shine as quick arguments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;banana&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fig&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cherry&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;    &lt;span class="c1"&gt;# ['fig', 'banana', 'cherry']
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two other built-ins that take functions are &lt;code&gt;filter&lt;/code&gt; (keep the items that pass a test) and &lt;code&gt;map&lt;/code&gt; (transform every item):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;    &lt;span class="c1"&gt;# [2, 4, 6]
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;            &lt;span class="c1"&gt;# [1, 4, 9, 16, 25, 36]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A word of advice. Keep lambdas short. If you need more than one simple expression, or if you'd like to give it a good name, write a regular &lt;code&gt;def&lt;/code&gt;. Also, in the next post you'll meet comprehensions, which are often a nicer way to do what &lt;code&gt;map&lt;/code&gt; and &lt;code&gt;filter&lt;/code&gt; do.&lt;/p&gt;

&lt;h2&gt;
  
  
  Closures: functions that remember
&lt;/h2&gt;

&lt;p&gt;Here's one that makes people pause the first time. You can define a function &lt;em&gt;inside&lt;/em&gt; another function, and the inner one can use the outer one's variables, even after the outer function has finished.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_multiplier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;factor&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;multiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;factor&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;multiply&lt;/span&gt;

&lt;span class="n"&gt;double&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;make_multiplier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;triple&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;make_multiplier&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;double&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;    &lt;span class="c1"&gt;# 10
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;triple&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;    &lt;span class="c1"&gt;# 15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When &lt;code&gt;make_multiplier(2)&lt;/code&gt; finishes, you'd think &lt;code&gt;factor&lt;/code&gt; disappears. It doesn't. The inner function carries it along. That's called a &lt;strong&gt;closure&lt;/strong&gt;, and &lt;code&gt;double&lt;/code&gt; and &lt;code&gt;triple&lt;/code&gt; each remember their own &lt;code&gt;factor&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It's useful any time you want to build a configured function. A more AI-flavored example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_prompt_builder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You are a &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;role&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. Answer this question: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;question&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;

&lt;span class="n"&gt;teacher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;make_prompt_builder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;patient teacher&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;teacher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What is a closure?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="c1"&gt;# You are a patient teacher. Answer this question: What is a closure?
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You set the role once, and then reuse the builder as many times as you like.&lt;/p&gt;

&lt;p&gt;A closure can also hold state that changes over time. For that you need the &lt;code&gt;nonlocal&lt;/code&gt; keyword, which says "I mean the variable from the outer function, not a new local one":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;make_counter&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;increment&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;nonlocal&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;
        &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;increment&lt;/span&gt;

&lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;make_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nf"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nf"&gt;counter&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;    &lt;span class="c1"&gt;# 1 2 3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Closures are the idea behind decorators, which is the topic of the next-but-one post. So if this section felt abstract, read it again slowly before moving on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Habits for writing good functions
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;One job per function.&lt;/strong&gt; If you need "and" to describe what it does, it's probably two functions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pick a clear name.&lt;/strong&gt; &lt;code&gt;calculate_cost&lt;/code&gt; beats &lt;code&gt;process&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Add a docstring.&lt;/strong&gt; A short description right under the &lt;code&gt;def&lt;/code&gt; line:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;calculate_cost&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price_per_million&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Return the cost in dollars for a number of tokens.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;price_per_million&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your editor will show it when you hover over the function, and your future self will thank you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mini project: a retry helper
&lt;/h2&gt;

&lt;p&gt;API calls fail. The network drops, a server is busy, you hit a rate limit. Real AI apps need to try again, and you don't want to copy the same retry code around every place you call a model.&lt;/p&gt;

&lt;p&gt;This is the perfect job for a higher-order function. Create &lt;code&gt;retry.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;func&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attempts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attempts&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Attempt &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;attempts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;raise&lt;/span&gt;
            &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;flaky_greeting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;0.6&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Service busy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flaky_greeting&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Asha&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attempts&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it a few times. Since the failures are random, the output changes. You might see something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;Attempt 1 failed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service busy&lt;/span&gt;
&lt;span class="na"&gt;Attempt 2 failed&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service busy&lt;/span&gt;
&lt;span class="s"&gt;Hello, Asha!&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Occasionally all five attempts fail and you'll see the error crash the program. That's expected. When the last attempt fails, the function re-raises the error instead of hiding it.&lt;/p&gt;

&lt;p&gt;Here's what's going on. &lt;code&gt;retry&lt;/code&gt; takes any function, plus whatever arguments that function needs, collected with &lt;code&gt;*args&lt;/code&gt; and &lt;code&gt;**kwargs&lt;/code&gt;. It calls the function inside a &lt;code&gt;try&lt;/code&gt; block. A &lt;code&gt;try&lt;/code&gt;/&lt;code&gt;except&lt;/code&gt; means "try this, and if it crashes, do this instead." (We'll cover it properly in Post 11.) Because &lt;code&gt;retry&lt;/code&gt; doesn't care what the function does, the same helper works for any function that might fail.&lt;/p&gt;

&lt;p&gt;One detail: &lt;code&gt;attempts&lt;/code&gt; and &lt;code&gt;delay&lt;/code&gt; come &lt;em&gt;after&lt;/em&gt; &lt;code&gt;*args&lt;/code&gt;. That means they can only be given by name, like &lt;code&gt;attempts=5&lt;/code&gt;. It stops you from mixing them up with the arguments for your own function.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make it yours
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Make the delay double after every failed attempt (this is called exponential backoff, and real APIs expect it).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Only retry on certain errors, and let others crash right away.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Print how long all the attempts took in total.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Practice
&lt;/h2&gt;

&lt;p&gt;About 20 minutes of real effort on each before you ask for help.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Write a function that takes any number of numbers and returns their average.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write &lt;code&gt;make_greeter(greeting)&lt;/code&gt; that returns a function. &lt;code&gt;make_greeter("Namaste")("Asha")&lt;/code&gt; should give &lt;code&gt;"Namaste, Asha!"&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Sort a list of names by their last letter, using a lambda.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write your own &lt;code&gt;apply_to_all(func, items)&lt;/code&gt; without using &lt;code&gt;map&lt;/code&gt; or a list comprehension.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fix this so repeated calls don't share a list:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;collect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{}):&lt;/span&gt;
    &lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then ask an AI tutor to review your answers, and don't let it rewrite them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to take away
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Arguments can be passed by position or by name, and &lt;code&gt;*args&lt;/code&gt; and &lt;code&gt;**kwargs&lt;/code&gt; collect the extras.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Never use a list or dictionary as a default value.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A variable made inside a function stays inside it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Functions are values, so you can store them, pass them, and return them.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lambdas are small one-line functions, best for short jobs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A closure is a function that remembers variables from where it was created.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Previous:&lt;/strong&gt; Post 5, &lt;em&gt;Built-in Data Structures&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next in the series:&lt;/strong&gt; Post 7, &lt;em&gt;Iterators, Generators &amp;amp; Comprehensions&lt;/em&gt;, the tools behind data pipelines and streaming model output.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Published via &lt;a href="https://zyvop.com/functions-in-depth-scope-closures-and-passing-functions-around-lbukq?utm_source=devto&amp;amp;utm_medium=crosspost&amp;amp;utm_campaign=syndication" rel="noopener noreferrer"&gt;ZyVOP&lt;/a&gt; — Write once in Markdown, auto-backup to GitHub, and syndicate to Dev.to, Medium &amp;amp; Hashnode in 1 click.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aiengineer</category>
      <category>closures</category>
      <category>functions</category>
      <category>programming</category>
    </item>
    <item>
      <title>Built-in Data Structures: Lists, Dicts, Sets, and Tuples</title>
      <dc:creator>Pradeep Kumar</dc:creator>
      <pubDate>Wed, 21 Oct 2026 06:18:37 +0000</pubDate>
      <link>https://dev.to/pradeep_kumar_bc4e7e9f7ec/built-in-data-structures-lists-dicts-sets-and-tuples-5fnh</link>
      <guid>https://dev.to/pradeep_kumar_bc4e7e9f7ec/built-in-data-structures-lists-dicts-sets-and-tuples-5fnh</guid>
      <description>&lt;p&gt;In the last post you stored single values: a name, a number, a piece of text. Real programs deal with collections. A list of chat messages. A dictionary of settings. A pile of documents where you need to know which ones you've already seen.&lt;/p&gt;

&lt;p&gt;Python gives you four built-in containers for this, and they cover most of what you'll write for a long time. Learning which one to reach for, and why, is one of the best early investments you can make.&lt;/p&gt;

&lt;p&gt;This post is skippable if you already know them well. Here's a test. What does this print?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;seen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;c&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you said 3, and you could explain why a set is a better choice than a list for "have I seen this before?" questions, skip to Post 6. Otherwise, keep reading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lists: an ordered collection you can change
&lt;/h2&gt;

&lt;p&gt;A list holds items in order. You can add to it, remove from it, and change it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;prompts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;summarize&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;translate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;explain&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;prompts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rewrite&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;        &lt;span class="c1"&gt;# summarize
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;       &lt;span class="c1"&gt;# rewrite
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompts&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;      &lt;span class="c1"&gt;# 4
&lt;/span&gt;
&lt;span class="n"&gt;prompts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;translate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;           &lt;span class="c1"&gt;# ['summarize', 'explain', 'rewrite']
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;      &lt;span class="c1"&gt;# ['explain', 'rewrite']
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Positions start at 0, just like with strings. A negative position counts from the end, so &lt;code&gt;-1&lt;/code&gt; is the last item. Slices like &lt;code&gt;[1:3]&lt;/code&gt; work the same way as on strings: from position 1 up to, but not including, position 3.&lt;/p&gt;

&lt;p&gt;Some other things you'll use all the time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;               &lt;span class="c1"&gt;# sorts the list itself: [1, 2, 5, 9]
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;       &lt;span class="c1"&gt;# returns a new sorted list, leaves the original alone
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;          &lt;span class="c1"&gt;# True
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also put anything in a list, including other lists and dictionaries. That's how chat history is usually stored when you talk to a language model:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hi&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;assistant&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello! How can I help?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A list of dictionaries. You'll see that exact shape over and over once we start building LLM apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dictionaries: look things up by name
&lt;/h2&gt;

&lt;p&gt;A dictionary stores pairs: a key and the value that goes with it. Instead of asking "what's at position 2?" you ask "what's the value for &lt;code&gt;temperature&lt;/code&gt;?"&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;settings&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;temperature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;temperature&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;     &lt;span class="c1"&gt;# 0.7
&lt;/span&gt;
&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_tokens&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;800&lt;/span&gt;       &lt;span class="c1"&gt;# change a value
&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stream&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;          &lt;span class="c1"&gt;# add a new pair
&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;model&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;         &lt;span class="c1"&gt;# True
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Loop over everything with &lt;code&gt;.items()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's a mistake almost every beginner makes. Asking for a key that isn't there with square brackets crashes your program with a &lt;code&gt;KeyError&lt;/code&gt;. The safer way is &lt;code&gt;.get()&lt;/code&gt;, which returns &lt;code&gt;None&lt;/code&gt; instead, or a default you choose:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;top_p&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;          &lt;span class="c1"&gt;# None
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;settings&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;top_p&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;     &lt;span class="c1"&gt;# 1.0
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dictionaries remember the order you added things in. And keys have to be things that can't change, like strings, numbers, and tuples. A list can't be a key.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tuples: a list you promise not to change
&lt;/h2&gt;

&lt;p&gt;A tuple looks like a list but with round brackets, and once you make it, it's fixed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;point&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;point&lt;/span&gt;        &lt;span class="c1"&gt;# unpacking: x is 3, y is 4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That second line is called unpacking, and it's one of the nicest things about Python. It's also how functions return more than one value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;min_max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min_max&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;low&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;high&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;     &lt;span class="c1"&gt;# 1 9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Under the hood, that function returns a tuple.&lt;/p&gt;

&lt;p&gt;Use a tuple when the thing has a fixed shape and shouldn't be edited: a coordinate, an RGB color, a width and height. You'll bump into them again soon. The shape of an array in NumPy or a tensor in PyTorch, like &lt;code&gt;(32, 128)&lt;/code&gt;, is a tuple.&lt;/p&gt;

&lt;p&gt;One quirk to remember: a tuple with a single item needs a trailing comma. &lt;code&gt;(5,)&lt;/code&gt; is a tuple. &lt;code&gt;(5)&lt;/code&gt; is just the number 5 in brackets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sets: unique things, no duplicates
&lt;/h2&gt;

&lt;p&gt;A set keeps only one copy of each item, and it doesn't care about order.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;c&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;unique&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;           &lt;span class="c1"&gt;# {'a', 'b', 'c'}  (the order can vary)
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;unique&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# True
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sets are also great at comparing groups:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# {1, 2, 3, 4}   everything in either
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# {3}            only what's in both
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# {1, 2}         in a but not in b
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A small trap: &lt;code&gt;{}&lt;/code&gt; makes an empty &lt;em&gt;dictionary&lt;/em&gt;, not an empty set. For an empty set, write &lt;code&gt;set()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which one should I use?
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;If you need...&lt;/th&gt;
&lt;th&gt;Use a...&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;items in order that you'll add to or change&lt;/td&gt;
&lt;td&gt;list&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;to look something up by a name or ID&lt;/td&gt;
&lt;td&gt;dictionary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;a small fixed group of values&lt;/td&gt;
&lt;td&gt;tuple&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;unique items, or fast "is it in here?" checks&lt;/td&gt;
&lt;td&gt;set&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Why speed differs (and why it matters)
&lt;/h2&gt;

&lt;p&gt;These containers don't just look different. They behave differently when they get big.&lt;/p&gt;

&lt;p&gt;Say you have a list with a million numbers and you ask, "is 999,999 in here?" Python walks through the list one item at a time until it finds it. A million steps. A set or dictionary works differently. It uses a trick called hashing, which turns the item into a number that tells Python roughly where to look. It jumps almost straight to the right spot, no matter how big the set is.&lt;/p&gt;

&lt;p&gt;Here's a quick way to see it for yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="n"&gt;numbers_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1_000_000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="n"&gt;numbers_set&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbers_list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="mi"&gt;999_999&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;numbers_list&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;list:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="mi"&gt;999_999&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;numbers_set&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;set: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a normal computer, the list check takes around ten milliseconds and the set check takes a couple of microseconds. That's thousands of times faster. Your exact numbers will be different, but the gap will be big.&lt;/p&gt;

&lt;p&gt;Programmers have a name for this idea: &lt;strong&gt;time complexity&lt;/strong&gt;, usually written as Big-O. We'll cover it properly in Post 18, but here's the simple version.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;List&lt;/th&gt;
&lt;th&gt;Dictionary&lt;/th&gt;
&lt;th&gt;Set&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Get by position&lt;/td&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Get by key&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Is this item in here?&lt;/td&gt;
&lt;td&gt;Slow (checks one by one)&lt;/td&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Add to the end&lt;/td&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;td&gt;Fast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Insert at the start&lt;/td&gt;
&lt;td&gt;Slow&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;"Fast" means the time barely changes as the collection grows. "Slow" means it grows along with the number of items. (For dictionaries and sets, "fast" is true on average, which is good enough for now.)&lt;/p&gt;

&lt;p&gt;Why should an AI engineer care? Imagine you're processing 100,000 documents and you want to skip ones you've already handled. If you keep the IDs in a list, each check can scan the whole list, which adds up to billions of comparisons by the end. If you keep them in a set, it's about 100,000 quick checks. Same result, wildly different wait.&lt;/p&gt;

&lt;h2&gt;
  
  
  A classic gotcha: copying a list
&lt;/h2&gt;

&lt;p&gt;Run this and look at the output carefully:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;# [1, 2, 3, 4]
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You added to &lt;code&gt;b&lt;/code&gt;, but &lt;code&gt;a&lt;/code&gt; changed too. That's because &lt;code&gt;b = a&lt;/code&gt; doesn't make a copy. It gives the same list a second name. Both names point to one list.&lt;/p&gt;

&lt;p&gt;To make a real copy, use &lt;code&gt;.copy()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That works for simple lists. If your list holds other lists or dictionaries, you'll later need &lt;code&gt;copy.deepcopy&lt;/code&gt;, which we'll meet when it matters. For now, just remember that assigning doesn't copy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mini project: a word counter and vocabulary
&lt;/h2&gt;

&lt;p&gt;When a language model reads text, one of the first things it needs is a vocabulary: the collection of unique pieces it knows about. Let's build a tiny version of that idea with the tools from this post.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;word_counter.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;The cat sat on the mat. The dog sat on the log.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;counts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

&lt;span class="c1"&gt;# Sort the words by how often they appear, highest first
&lt;/span&gt;&lt;span class="n"&gt;ranked_words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ranked_words&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;counts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;vocabulary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Unique words: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vocabulary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="n"&gt;the&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="n"&gt;sat&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="k"&gt;on&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="n"&gt;cat&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="n"&gt;mat&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;Unique&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The heart of it is this line: &lt;code&gt;counts[word] = counts.get(word, 0) + 1&lt;/code&gt;. It means "look up the current count for this word, use 0 if it isn't there yet, add one, and save it back." It's a pattern you'll use constantly.&lt;/p&gt;

&lt;p&gt;In the sorting line, &lt;code&gt;key=counts.get&lt;/code&gt; tells Python to sort the words by their counts instead of alphabetically. Don't worry if that feels odd. Post 6 explains why you can pass a function around like that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make it yours
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Replace the sample text with a real text file, such as a chapter from a book.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make a set of "stop words" like &lt;code&gt;the&lt;/code&gt;, &lt;code&gt;a&lt;/code&gt;, and &lt;code&gt;on&lt;/code&gt;, and skip them when counting.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Print the ten most common words instead of five.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When you're done, look up &lt;code&gt;collections.Counter&lt;/code&gt; in the Python docs. It does the counting loop for you. It's nice to meet a shortcut after you've done it the long way.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Practice
&lt;/h2&gt;

&lt;p&gt;About 20 minutes of honest effort on each before you ask for help.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Reverse a list without using &lt;code&gt;.reverse()&lt;/code&gt; or &lt;code&gt;[::-1]&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write a function that takes a list of numbers and returns the smallest and largest as a tuple.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Remove duplicates from a list but keep the original order. (Hint: a set can remember what you've already seen.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Swap the keys and values of a dictionary.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Given two lists of words, print the words they have in common.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As before, once you've finished, ask an AI tutor to review your code, not rewrite it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to take away
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Lists&lt;/strong&gt; keep things in order and can be changed.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Dictionaries&lt;/strong&gt; let you look things up by key.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tuples&lt;/strong&gt; are fixed groups of values, and make returning several things easy.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Sets&lt;/strong&gt; keep unique items and are very fast for "is it in here?" checks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Picking the right container can turn a program that crawls into one that flies.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Writing &lt;code&gt;b = a&lt;/code&gt; doesn't copy a list.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Previous:&lt;/strong&gt; Post 4, &lt;em&gt;Python Fundamentals&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next in the series:&lt;/strong&gt; Post 6, &lt;em&gt;Functions in Depth&lt;/em&gt;, where we go beyond the basics: scope, closures, flexible arguments, and passing functions around.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Published via &lt;a href="https://zyvop.com/built-in-data-structures-lists-dicts-sets-and-tuples-t1yim?utm_source=devto&amp;amp;utm_medium=crosspost&amp;amp;utm_campaign=syndication" rel="noopener noreferrer"&gt;ZyVOP&lt;/a&gt; — Write once in Markdown, auto-backup to GitHub, and syndicate to Dev.to, Medium &amp;amp; Hashnode in 1 click.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aiengineer</category>
      <category>beginners</category>
      <category>datastructures</category>
      <category>programming</category>
    </item>
    <item>
      <title>Python Fundamentals: The Basics You Need Before Anything Else</title>
      <dc:creator>Pradeep Kumar</dc:creator>
      <pubDate>Wed, 21 Oct 2026 06:18:08 +0000</pubDate>
      <link>https://dev.to/pradeep_kumar_bc4e7e9f7ec/python-fundamentals-the-basics-you-need-before-anything-else-4hi5</link>
      <guid>https://dev.to/pradeep_kumar_bc4e7e9f7ec/python-fundamentals-the-basics-you-need-before-anything-else-4hi5</guid>
      <description>&lt;p&gt;Almost everything in AI engineering is written in Python. The models, the libraries, the APIs, the tutorials. So this is where the coding starts.&lt;/p&gt;

&lt;p&gt;This post is for people who are new to Python. If you already write loops and functions without looking things up, you can skip it. Here's a quick test. What does this print?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you said 6 without running it, skip ahead to Post 6. If that code looked like a puzzle, stay. By the end of this post you'll read it easily.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Python running
&lt;/h2&gt;

&lt;p&gt;You need two things: Python itself, and a place to write code.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Install a recent version of Python (3.11 or newer) from python.org.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Install &lt;a href="https://code.visualstudio.com/" rel="noopener noreferrer"&gt;VS Code&lt;/a&gt; as your editor. It's free and works well for this.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Open a terminal and check it worked:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you see a version number, you're set. On some systems the command is &lt;code&gt;python3&lt;/code&gt; instead of &lt;code&gt;python&lt;/code&gt;. Use whichever one works.&lt;/p&gt;

&lt;p&gt;To run a file, save your code as &lt;code&gt;hello.py&lt;/code&gt; and type:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python hello.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also type &lt;code&gt;python&lt;/code&gt; on its own to open a little playground where you test one line at a time. It's perfect for quick experiments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Variables: names for things
&lt;/h2&gt;

&lt;p&gt;A variable is a name that points to a value.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Asha&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;29&lt;/span&gt;
&lt;span class="n"&gt;height&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.62&lt;/span&gt;
&lt;span class="n"&gt;is_learning&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
&lt;span class="n"&gt;nothing_yet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's five variables, and you never had to say what type each one is. Python figures it out. That's one reason people find it easy to start with.&lt;/p&gt;

&lt;p&gt;A few habits to pick up now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Use &lt;code&gt;snake_case&lt;/code&gt; for names: &lt;code&gt;total_cost&lt;/code&gt;, not &lt;code&gt;TotalCost&lt;/code&gt; or &lt;code&gt;totalcost&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make names say what the thing is. &lt;code&gt;price&lt;/code&gt; is better than &lt;code&gt;p&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Names are case sensitive. &lt;code&gt;Age&lt;/code&gt; and &lt;code&gt;age&lt;/code&gt; are different variables.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The basic types
&lt;/h2&gt;

&lt;p&gt;You'll use these five constantly:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;What it's for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;int&lt;/td&gt;
&lt;td&gt;42&lt;/td&gt;
&lt;td&gt;whole numbers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;float&lt;/td&gt;
&lt;td&gt;3.14&lt;/td&gt;
&lt;td&gt;numbers with decimals&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;str&lt;/td&gt;
&lt;td&gt;"hello"&lt;/td&gt;
&lt;td&gt;text&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bool&lt;/td&gt;
&lt;td&gt;True, False&lt;/td&gt;
&lt;td&gt;yes or no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;"nothing here yet"&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Check the type of anything with &lt;code&gt;type()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;      &lt;span class="c1"&gt;# &amp;lt;class 'int'&amp;gt;
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;     &lt;span class="c1"&gt;# &amp;lt;class 'str'&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One quirk you should know about early, because it surprises everyone:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c1"&gt;# 0.30000000000000004
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's not a Python bug. Computers store decimals in a way that can't represent some numbers exactly, and you'll see the same thing in almost every language. It matters later when you compare numbers in AI code, so keep it in the back of your mind.&lt;/p&gt;

&lt;h2&gt;
  
  
  Working with text
&lt;/h2&gt;

&lt;p&gt;Text, called a string, comes up in every AI project. Prompts are strings. Model replies are strings. So get comfortable with them.&lt;/p&gt;

&lt;p&gt;The nicest way to put values inside text is an f-string. Put an &lt;code&gt;f&lt;/code&gt; before the quotes and wrap variables in curly braces:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; is &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; years old&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# Asha is 29 years old
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Strings also come with useful built-in tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;  Hello, World  &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;         &lt;span class="c1"&gt;# Hello, World
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;         &lt;span class="c1"&gt;# "  hello, world  "
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;      &lt;span class="c1"&gt;# ['  Hello', ' World  ']
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;    &lt;span class="c1"&gt;# Hello
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;strip()&lt;/code&gt; removes extra spaces from the ends. &lt;code&gt;split()&lt;/code&gt; cuts a string into pieces. And &lt;code&gt;[0:5]&lt;/code&gt; is a slice, which means "from position 0 up to, but not including, position 5." Python counts from zero, which feels odd for about a week and then feels normal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making decisions with if
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Excellent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pass&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Try again&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things to notice. First, the colon at the end of each condition line. Second, the &lt;strong&gt;indentation&lt;/strong&gt;. In Python, the spaces at the start of a line aren't decoration. They tell Python which code belongs inside the &lt;code&gt;if&lt;/code&gt;. Use four spaces, and your editor will usually do it for you when you press Tab.&lt;/p&gt;

&lt;p&gt;Here's a handy fact. Python treats some values as "false" even when they aren't literally &lt;code&gt;False&lt;/code&gt;: &lt;code&gt;0&lt;/code&gt;, an empty string &lt;code&gt;""&lt;/code&gt;, &lt;code&gt;None&lt;/code&gt;, and empty lists. Everything else counts as true. So you can write:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;reply&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;The model returned nothing&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll write checks like that all the time when handling model output.&lt;/p&gt;

&lt;h2&gt;
  
  
  Repeating things with loops
&lt;/h2&gt;

&lt;p&gt;A &lt;code&gt;for&lt;/code&gt; loop runs code once for each item:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# 1
# 2
# 3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;range(1, 4)&lt;/code&gt; gives 1, 2, 3. It stops &lt;em&gt;before&lt;/em&gt; the second number, the same way slices do.&lt;/p&gt;

&lt;p&gt;You can loop over a list of things too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;python&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fun&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A &lt;code&gt;while&lt;/code&gt; loop keeps going as long as something stays true:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;

&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Be careful with &lt;code&gt;while&lt;/code&gt;. If nothing ever makes the condition false, the loop runs forever. If that happens, press &lt;code&gt;Ctrl + C&lt;/code&gt; to stop it. It happens to everyone.&lt;/p&gt;

&lt;p&gt;Two tools worth knowing: &lt;code&gt;break&lt;/code&gt; leaves a loop early, and &lt;code&gt;continue&lt;/code&gt; skips to the next round.&lt;/p&gt;

&lt;p&gt;Now go back to the puzzle at the top. &lt;code&gt;range(1, 6)&lt;/code&gt; gives 1 to 5, &lt;code&gt;n % 2 == 0&lt;/code&gt; checks if a number is even (&lt;code&gt;%&lt;/code&gt; gives the remainder after dividing), and &lt;code&gt;total += n&lt;/code&gt; means "add n to total." So it adds 2 and 4, and prints 6.&lt;/p&gt;

&lt;h2&gt;
  
  
  Functions: reusable chunks
&lt;/h2&gt;

&lt;p&gt;A function packages some code under a name so you can use it again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hello, &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Asha&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;     &lt;span class="c1"&gt;# Hello, Asha!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;def&lt;/code&gt; starts the function, &lt;code&gt;name&lt;/code&gt; is its input (called a parameter), and &lt;code&gt;return&lt;/code&gt; hands back the result.&lt;/p&gt;

&lt;p&gt;You can give a parameter a default value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;power&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exponent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;exponent&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;power&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;        &lt;span class="c1"&gt;# 25
&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;power&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;     &lt;span class="c1"&gt;# 125
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We'll go much deeper into functions in Post 6. For now, the main idea is enough: write a function whenever you catch yourself copying the same code twice.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Python actually runs your code
&lt;/h2&gt;

&lt;p&gt;You don't need this to write programs, but it explains a lot later, so here's the short version.&lt;/p&gt;

&lt;p&gt;When you run &lt;code&gt;python hello.py&lt;/code&gt;, Python reads your file from top to bottom. Behind the scenes it first turns your code into a simpler set of instructions called bytecode, and then a program called the Python interpreter carries out those instructions one at a time.&lt;/p&gt;

&lt;p&gt;That makes Python quick to write and easy to debug, but slower than languages like C when you're doing raw number crunching in loops.&lt;/p&gt;

&lt;p&gt;So why is Python the language of AI? Because the heavy work doesn't happen in Python. Libraries like NumPy and PyTorch do the heavy math in fast C, C++, and GPU code. Your Python code just tells them what to do. It's a bit like a conductor, who doesn't play every instrument but makes the orchestra work together. You get the easy language and the speed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reading error messages
&lt;/h2&gt;

&lt;p&gt;You'll see a lot of errors. That's normal and it never fully goes away, even for experienced people. The trick is to read them properly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;29&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Age: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nb"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;can&lt;/span&gt; &lt;span class="n"&gt;only&lt;/span&gt; &lt;span class="n"&gt;concatenate&lt;/span&gt; &lt;span class="nf"&gt;str &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;int&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;to&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read the &lt;strong&gt;last line first&lt;/strong&gt;. It tells you what went wrong: you tried to join text and a number. Then look at the line number above it to see where. The fix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Age: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few errors you'll meet early:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;NameError&lt;/code&gt;: you used a name that doesn't exist, usually a typo.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;TypeError&lt;/code&gt;: you mixed types that don't go together.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;IndentationError&lt;/code&gt;: your spacing is off.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;SyntaxError&lt;/code&gt;: something is missing, often a colon or a closing bracket.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'll cover debugging properly in Post 11.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mini project: a token and cost estimator
&lt;/h2&gt;

&lt;p&gt;Language models don't read words. They read &lt;em&gt;tokens&lt;/em&gt;, which are small pieces of text. And you're charged by the token when you use most model APIs. So estimating tokens is useful from day one.&lt;/p&gt;

&lt;p&gt;A rough rule of thumb for English is about 4 characters per token. It's not exact, but it's close enough to get a feel for size and cost.&lt;/p&gt;

&lt;p&gt;Create a file called &lt;code&gt;token_estimator.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Paste some text: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;characters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="c1"&gt;# Rough rule of thumb: about 4 characters per token in English
&lt;/span&gt;&lt;span class="n"&gt;estimated_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;characters&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;

&lt;span class="c1"&gt;# Made-up price. Check your provider for real numbers.
&lt;/span&gt;&lt;span class="n"&gt;price_per_million_tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;3.00&lt;/span&gt;
&lt;span class="n"&gt;estimated_cost&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;estimated_tokens&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1_000_000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;price_per_million_tokens&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Characters: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;characters&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Words: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Estimated tokens: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;estimated_tokens&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Estimated cost: $&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;estimated_cost&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it with &lt;code&gt;python token_estimator.py&lt;/code&gt;, paste in a paragraph, and see what comes out.&lt;/p&gt;

&lt;p&gt;A few new bits in there: &lt;code&gt;len()&lt;/code&gt; counts how many characters or items something has, &lt;code&gt;input()&lt;/code&gt; asks the user to type something, and &lt;code&gt;:.0f&lt;/code&gt; inside an f-string means "show this number with no decimals."&lt;/p&gt;

&lt;p&gt;The price is invented on purpose. Real prices change often and differ between models, so never trust a number from a blog post. Look it up for the one you're using.&lt;/p&gt;

&lt;h3&gt;
  
  
  Make it yours
&lt;/h3&gt;

&lt;p&gt;Change the project so that it isn't just my example anymore:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Print a warning if the estimate goes above 1,000 tokens.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Read the text from a file instead of asking you to paste it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Wrap it in a &lt;code&gt;while&lt;/code&gt; loop so you can check many texts, and stop when you type &lt;code&gt;quit&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Practice
&lt;/h2&gt;

&lt;p&gt;Try these without looking anything up. Aim for about 20 minutes of honest effort before you ask for help.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Ask for the user's name and print a greeting.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Convert a temperature from Celsius to Fahrenheit.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Count the vowels in a sentence.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Print the numbers from 1 to 20, but print "Fizz" for multiples of 3.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write a function &lt;code&gt;is_even(n)&lt;/code&gt; that returns &lt;code&gt;True&lt;/code&gt; or &lt;code&gt;False&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;When you finish, paste your solution into an AI tutor and ask it to &lt;em&gt;review&lt;/em&gt; the code, not rewrite it. That's the habit from Post 3, and it works.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to take away
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Variables are names for values, and Python figures out the type for you.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Strings, numbers, booleans, and &lt;code&gt;None&lt;/code&gt; cover most of what you'll use early on.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Indentation is part of the syntax.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Functions let you reuse code instead of copying it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Python is mostly the glue. The fast work happens in libraries underneath.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Read error messages from the last line up.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Previous:&lt;/strong&gt; Post 3, &lt;em&gt;How to Learn AI Faster&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next in the series:&lt;/strong&gt; Post 5, &lt;em&gt;Built-in Data Structures&lt;/em&gt;, where we learn lists, dictionaries, sets, and tuples, and how fast each one is.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Published via &lt;a href="https://zyvop.com/python-fundamentals-the-basics-you-need-before-anything-else-w48xn?utm_source=devto&amp;amp;utm_medium=crosspost&amp;amp;utm_campaign=syndication" rel="noopener noreferrer"&gt;ZyVOP&lt;/a&gt; — Write once in Markdown, auto-backup to GitHub, and syndicate to Dev.to, Medium &amp;amp; Hashnode in 1 click.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aiengineer</category>
      <category>beginners</category>
      <category>programming</category>
      <category>python</category>
    </item>
    <item>
      <title>How to Learn AI Faster (Without Getting Stuck in Tutorial Hell)</title>
      <dc:creator>Pradeep Kumar</dc:creator>
      <pubDate>Mon, 19 Oct 2026 06:53:04 +0000</pubDate>
      <link>https://dev.to/pradeep_kumar_bc4e7e9f7ec/how-to-learn-ai-faster-without-getting-stuck-in-tutorial-hell-25b7</link>
      <guid>https://dev.to/pradeep_kumar_bc4e7e9f7ec/how-to-learn-ai-faster-without-getting-stuck-in-tutorial-hell-25b7</guid>
      <description>&lt;p&gt;Most people don't give up on AI because it's too hard. They give up because they got stuck in something I'll call tutorial hell, and they never noticed.&lt;/p&gt;

&lt;p&gt;Here's how it goes. You watch a six-hour course. Everything makes sense while the instructor types. You nod along, you feel smart, you finish the last video. Then you open a blank file and can't write a single line.&lt;/p&gt;

&lt;p&gt;If that sounds familiar, good news: it's fixable, and this post is how.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why tutorial hell happens
&lt;/h2&gt;

&lt;p&gt;Watching someone do something and doing it yourself use different parts of your brain. When you watch, you recognize. When you build, you &lt;em&gt;recall&lt;/em&gt;, and recall is the thing you need on the job. Recognizing the answer is easy. Producing it from nothing is hard, and that hard feeling is exactly what makes you learn.&lt;/p&gt;

&lt;p&gt;So the rule for this whole series is simple: &lt;strong&gt;if it feels a little uncomfortable, it's working.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Build something every week
&lt;/h2&gt;

&lt;p&gt;After every post, make something small that isn't the example in the post. Change one thing about it.&lt;/p&gt;

&lt;p&gt;The post builds a token counter? You build one that also tells you the cost. The post calls an API to summarize text? You point it at your own notes. The goal is not a great project. The goal is making decisions that nobody made for you.&lt;/p&gt;

&lt;p&gt;Keep it small enough to finish in a few hours. A finished ugly project beats a beautiful one that's never done.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Type the code, don't paste it
&lt;/h2&gt;

&lt;p&gt;When you copy and paste, your hands move but your brain checks out. Type it, even when it feels slow. You'll make typos, and fixing those typos teaches you more than you'd think.&lt;/p&gt;

&lt;p&gt;And when you hit a bug, give yourself about 20 minutes to fight it before asking for help. That struggle is where the learning lives. After 20 minutes with no progress, ask. Don't suffer for hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Use AI as a tutor, not a ghostwriter
&lt;/h2&gt;

&lt;p&gt;This one matters a lot, since you're learning to build AI and you also have AI to help you learn. It's an incredible tutor. It's also an easy way to fool yourself into thinking you learned something.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Good ways to use it:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;"Quiz me on Python generators, one question at a time. Don't give me the answer until I've tried."&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;"Here's my code. Don't fix it. Just tell me which part to look at."&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;"Explain this like I'm new to it. Then give me one harder follow-up question."&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;em&gt;"Here's how I understand it. Where am I wrong?"&lt;/em&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Not so good ways:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Asking it to write the whole project, then pasting the result.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Accepting an answer you don't understand, just because it sounds confident.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two more things to keep in mind. First, AI can be confidently wrong. This happens most with new library versions and fast-changing tools, which is a lot of what we'll use. So &lt;strong&gt;always run the code&lt;/strong&gt;, and check the official docs when something matters. Second, here's a simple test of whether you learned or just copied: tomorrow, can you rebuild it with nothing open? If yes, you learned it. If not, go back.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Explain it back
&lt;/h2&gt;

&lt;p&gt;After you learn something, try to explain it in your own words, out loud or in writing, as if to a friend who knows nothing about it. Anywhere you stumble is a gap in your understanding, and now you know where to look.&lt;/p&gt;

&lt;p&gt;It sounds too simple to be useful. It's one of the most reliable tricks there is.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Build in public
&lt;/h2&gt;

&lt;p&gt;This sounds scarier than it is. It doesn't mean going viral. It means leaving a trail.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Push your code to GitHub as you go.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write one short note a week about what you learned or what broke.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Share it somewhere: a blog, LinkedIn, a dev community, even a private repo at first.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don't need an audience. Having &lt;em&gt;anyone&lt;/em&gt; in mind, even an imaginary reader, makes you finish things and explain them clearly. And down the line, that trail becomes your portfolio. A hiring manager scrolling through a year of honest weekly notes learns more about you than any resume could show them.&lt;/p&gt;

&lt;p&gt;A great post doesn't have to be impressive. "Week 6: I wrote my first async script, it broke in three different ways, here's what I learned" is a perfectly good post.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Keep a bug diary
&lt;/h2&gt;

&lt;p&gt;Every time something breaks, write down three lines: what happened, what caused it, how you fixed it. In a few months you'll have a personal handbook of the exact mistakes &lt;em&gt;you&lt;/em&gt; tend to make. You'll also notice you're making fewer of them, which is a nice thing to see on paper.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Review a little, often
&lt;/h2&gt;

&lt;p&gt;Memory fades. That's normal, not a sign you're bad at this. Once a week, spend 15 minutes looking back at something from a few weeks ago. Re-read your notes, redo a small exercise, or have an AI tutor quiz you. Seeing something again right when you're about to forget it is what makes it stick.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Collecting courses.&lt;/strong&gt; Bookmarking five more courses feels like progress. It isn't. Pick one resource per topic and finish it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Switching tools constantly.&lt;/strong&gt; A new framework appears every week. Ignore most of them while you learn the basics.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Waiting until you feel ready.&lt;/strong&gt; You won't feel ready. Start before you do.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Comparing yourself to people online.&lt;/strong&gt; You're seeing their best moments, at their current level, after years of work you can't see.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Avoiding the math completely.&lt;/strong&gt; It's less scary than it looks, and we'll take it slowly in Phase 2.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Marathon weekends followed by silence.&lt;/strong&gt; Regular beats intense.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What "faster" really means
&lt;/h2&gt;

&lt;p&gt;It doesn't mean cramming. It means wasting less time: less time watching without doing, less time stuck alone, less time re-learning things you forgot. A steady five hours a week beats twenty hours once a month, every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try this today (20 minutes)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create a GitHub account if you don't have one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make a new repository called &lt;code&gt;ai-engineer-journey&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add a README with your start date, your goal, and the weekly study slots you picked in the last post.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make your first commit.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You've now started building in public, and you did it before writing a single line of code. The streak starts today.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Previous:&lt;/strong&gt; &lt;a href="https://zyvop.com/the-12-month-roadmap-zero-to-ai-engineer-m6rcu" rel="noopener noreferrer"&gt;Post 2, &lt;em&gt;The 12-Month Roadmap&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next in the series:&lt;/strong&gt; Post 4, &lt;em&gt;Python Fundamentals&lt;/em&gt;, where we start writing real code.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Published via &lt;a href="https://zyvop.com/how-to-learn-ai-faster-without-getting-stuck-in-tutorial-hell-ncs1v?utm_source=devto&amp;amp;utm_medium=crosspost&amp;amp;utm_campaign=syndication" rel="noopener noreferrer"&gt;ZyVOP&lt;/a&gt; — Write once in Markdown, auto-backup to GitHub, and syndicate to Dev.to, Medium &amp;amp; Hashnode in 1 click.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aitutor</category>
      <category>beginners</category>
      <category>buildinpublic</category>
      <category>learning</category>
    </item>
    <item>
      <title>The 12-Month Roadmap: Zero to AI Engineer</title>
      <dc:creator>Pradeep Kumar</dc:creator>
      <pubDate>Mon, 12 Oct 2026 06:18:52 +0000</pubDate>
      <link>https://dev.to/pradeep_kumar_bc4e7e9f7ec/the-12-month-roadmap-zero-to-ai-engineer-jd9</link>
      <guid>https://dev.to/pradeep_kumar_bc4e7e9f7ec/the-12-month-roadmap-zero-to-ai-engineer-jd9</guid>
      <description>&lt;p&gt;A year sounds like plenty of time until you look at 79 posts and wonder how any of it fits. This post is the plan. Read it once now, then come back whenever you feel lost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Read this first
&lt;/h2&gt;

&lt;p&gt;The schedule below assumes two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;You can give it &lt;strong&gt;about 8 to 10 hours a week&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You're starting with &lt;strong&gt;little or no coding experience&lt;/strong&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If either is different for you, the plan still works. It just stretches or shrinks. Some people will finish in nine months, others in eighteen, and both are fine.&lt;/p&gt;

&lt;p&gt;Also, this is a plan, not a promise. Nobody can guarantee a job at the end of twelve months, because it depends on the market, your background, and how much you build. What I can say is that if you follow this and finish the projects, you'll have real skills and real proof of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The year in one table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Months&lt;/th&gt;
&lt;th&gt;Weeks&lt;/th&gt;
&lt;th&gt;Phase&lt;/th&gt;
&lt;th&gt;Posts&lt;/th&gt;
&lt;th&gt;What you'll have made&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1 to 3&lt;/td&gt;
&lt;td&gt;1 to 14&lt;/td&gt;
&lt;td&gt;Orientation + Programming in Depth&lt;/td&gt;
&lt;td&gt;1 to 30&lt;/td&gt;
&lt;td&gt;A CLI tool, a tested REST API, and a Python package on PyPI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;15 to 18&lt;/td&gt;
&lt;td&gt;Math &amp;amp; Data for AI&lt;/td&gt;
&lt;td&gt;31 to 37&lt;/td&gt;
&lt;td&gt;A data analysis notebook on GitHub&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;19 to 22&lt;/td&gt;
&lt;td&gt;Machine Learning &amp;amp; Deep Learning&lt;/td&gt;
&lt;td&gt;38 to 42&lt;/td&gt;
&lt;td&gt;A trained image or text classifier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;23 to 26&lt;/td&gt;
&lt;td&gt;Transformers &amp;amp; LLMs Under the Hood&lt;/td&gt;
&lt;td&gt;43 to 48&lt;/td&gt;
&lt;td&gt;A tiny GPT you built yourself&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7 to 8&lt;/td&gt;
&lt;td&gt;27 to 34&lt;/td&gt;
&lt;td&gt;Building with LLMs&lt;/td&gt;
&lt;td&gt;49 to 59&lt;/td&gt;
&lt;td&gt;A deployed RAG or agent app with a public demo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;35 to 39&lt;/td&gt;
&lt;td&gt;Evaluation, Safety, and Customizing Models&lt;/td&gt;
&lt;td&gt;60 to 67&lt;/td&gt;
&lt;td&gt;An eval suite and a fine-tuned open model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;40 to 43&lt;/td&gt;
&lt;td&gt;Production &amp;amp; Scale&lt;/td&gt;
&lt;td&gt;68 to 71&lt;/td&gt;
&lt;td&gt;Your app running in the cloud with tracing, caching, and cost limits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11 to 12&lt;/td&gt;
&lt;td&gt;44 to 52&lt;/td&gt;
&lt;td&gt;Career &amp;amp; Becoming Top-Tier&lt;/td&gt;
&lt;td&gt;72 to 79&lt;/td&gt;
&lt;td&gt;A portfolio, a resume, interview practice, and real applications&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Month by month
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Months 1 to 3: Learn to code properly.&lt;/strong&gt; This is the longest phase and the least glamorous. You'll learn Python well, the computer science that actually matters, and the daily tools like Git and Docker. It feels slow. Stick with it anyway, because every later phase leans on it. People who rush through this part tend to hit a wall around PyTorch and async code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Month 4: Math and data.&lt;/strong&gt; Vectors, gradients, probability, and working with real messy data. We'll keep it intuitive. You won't be proving theorems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Month 5: Machine learning.&lt;/strong&gt; Classic ML first, then neural networks from scratch, then PyTorch. This is where AI stops feeling like magic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Month 6: Inside the transformer.&lt;/strong&gt; Tokenizers, attention, and building a small GPT. You won't build a model that rivals the big ones, but you'll understand why they work the way they do, and that understanding pays off every time something breaks later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Months 7 and 8: Build with LLMs.&lt;/strong&gt; The core of the job. API calls, tool use, search over documents, agents, MCP, voice and images. You finish with a deployed app that someone else can try.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Month 9: Test it and make it yours.&lt;/strong&gt; How to measure whether your app is good, how to keep it safe, and when (and when not) to fine-tune a model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Month 10: Ship it for real.&lt;/strong&gt; Speed, cost, GPUs, cloud, and keeping things running.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Months 11 and 12: Get hired.&lt;/strong&gt; Portfolio, resume, interviews, open source, and choosing a direction. You should be applying by the middle of month 11, not waiting until you feel "ready."&lt;/p&gt;

&lt;h2&gt;
  
  
  A weekly rhythm that works
&lt;/h2&gt;

&lt;p&gt;Pick a rhythm and stick to it. Here's one that fits in roughly 9 hours:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Early week (about 3 hours):&lt;/strong&gt; read the post and code along. Type everything yourself.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Late week (about 5 hours):&lt;/strong&gt; build the mini-project, or change the example into something of your own.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Weekend (about 1 hour):&lt;/strong&gt; write down what you learned, push your code to GitHub, and skim your notes from last week.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Short, regular sessions beat one giant weekend. Five hours spread across the week will teach you more than fifteen hours crammed into one day, mostly because your brain needs sleep to keep things.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where should you start? Pick your path
&lt;/h2&gt;

&lt;p&gt;Not everyone needs all 79 posts.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;If you are...&lt;/th&gt;
&lt;th&gt;Do this&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A complete beginner&lt;/td&gt;
&lt;td&gt;Read posts 1 to 79 in order.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Already able to code&lt;/td&gt;
&lt;td&gt;Read posts 1 to 3, skim posts 10, 13, and 14 (types, testing, concurrency), then jump to post 31. You'll finish in about nine months, or sooner.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Already know ML&lt;/td&gt;
&lt;td&gt;Read posts 1 to 3, skim posts 11 and 14, then jump to post 43 or 49. Closer to six months.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you're not sure which one you are, here's a quick test. Can you write a small Python program that reads a file, calls a web API, and handles errors, without copying it from somewhere? If yes, you can code. If not, start at the beginning. There's no shame in either answer.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to know you're on track
&lt;/h2&gt;

&lt;p&gt;Check yourself at these points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;End of month 3:&lt;/strong&gt; you can write and test a small Python program from a blank file, without copying.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;End of month 6:&lt;/strong&gt; you can explain, in plain words, how a model produces the next word.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;End of month 9:&lt;/strong&gt; you have a live app, and numbers that show how well it works.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;End of month 12:&lt;/strong&gt; you've applied for roles, and you have a portfolio you're proud of.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Miss one by a few weeks? Totally normal. Shift the schedule, don't quit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four rules for finishing
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Don't skip the projects.&lt;/strong&gt; Reading feels like progress. Building is progress.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;If you're stuck for two hours, ask someone.&lt;/strong&gt; A forum, a friend, an AI tutor (the next post is about using one well). Hours of silent struggle teach less than people think.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;When you fall behind, move the dates.&lt;/strong&gt; Don't throw out the plan.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Don't chase every new tool.&lt;/strong&gt; New models and libraries will appear every month while you're doing this. Most of them won't matter for your first job. The ideas underneath will.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Try this today (15 minutes)
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Decide your start date. Tomorrow is fine.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Block out your weekly study slots in your calendar, the actual hours, with real times.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write down which path you're taking: beginner, can code, or knows ML.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's it. A plan with a date and calendar slots beats a plan that lives in your head.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Previous:&lt;/strong&gt; Post 1, &lt;em&gt;What Is an AI Engineer in 2026?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Next in the series:&lt;/strong&gt; Post 3, &lt;em&gt;How to Learn AI Faster&lt;/em&gt;, including how to use AI as a tutor without letting it do your homework.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Published via &lt;a href="https://zyvop.com/the-12-month-roadmap-zero-to-ai-engineer-m6rcu?utm_source=devto&amp;amp;utm_medium=crosspost&amp;amp;utm_campaign=syndication" rel="noopener noreferrer"&gt;ZyVOP&lt;/a&gt; — Write once in Markdown, auto-backup to GitHub, and syndicate to Dev.to, Medium &amp;amp; Hashnode in 1 click.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aiengineer</category>
      <category>beginners</category>
      <category>learningpath</category>
      <category>roadmap</category>
    </item>
    <item>
      <title>What Is an AI Engineer in 2026?</title>
      <dc:creator>Pradeep Kumar</dc:creator>
      <pubDate>Sun, 11 Oct 2026 07:05:20 +0000</pubDate>
      <link>https://dev.to/pradeep_kumar_bc4e7e9f7ec/what-is-an-ai-engineer-in-2026-kg3</link>
      <guid>https://dev.to/pradeep_kumar_bc4e7e9f7ec/what-is-an-ai-engineer-in-2026-kg3</guid>
      <description>&lt;p&gt;Ask five people what an AI engineer does and you'll get five different answers. Part of the reason is that the job is new. The other part is that companies stick the title on roles that have almost nothing in common.&lt;/p&gt;

&lt;p&gt;So before you spend a year learning anything, let's figure out what this job really is, what it isn't, and whether it's the one you want.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short version
&lt;/h2&gt;

&lt;p&gt;An AI engineer builds real products using AI models that already exist.&lt;/p&gt;

&lt;p&gt;That's the whole idea. A handful of big labs and open-source groups train the giant models. AI engineers take those models and turn them into things people actually use. A support bot that answers questions correctly. A tool that reads contracts and flags the risky parts. A coding assistant. A voice agent that books appointments.&lt;/p&gt;

&lt;p&gt;The work sits between software engineering and machine learning, and it leans a lot closer to software engineering than most beginners expect. If you can build a solid app and you understand how these models behave, you're already most of the way there.&lt;/p&gt;

&lt;h2&gt;
  
  
  The restaurant comparison
&lt;/h2&gt;

&lt;p&gt;The easiest way I know to explain the different roles is a restaurant.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The research scientist&lt;/strong&gt; invents new ways of cooking. New techniques, new ingredients. They write papers about it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The ML engineer&lt;/strong&gt; builds the kitchen that can produce that new ingredient in huge amounts, every day, without it going wrong. Training pipelines, GPUs, deployment.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The data scientist&lt;/strong&gt; studies the orders. Which dishes sell, why customers stop coming back, whether the new menu actually helped.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The AI engineer&lt;/strong&gt; is the chef. They take the best ingredients available and cook dishes people want to eat, then make sure those dishes come out right every single night.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In real life the lines blur. At a small startup one person might do three of these jobs before lunch. But it gives you a map to start with.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same thing, in a table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;The question they ask&lt;/th&gt;
&lt;th&gt;Typical work&lt;/th&gt;
&lt;th&gt;Common tools&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Research scientist&lt;/td&gt;
&lt;td&gt;Can we make models fundamentally better?&lt;/td&gt;
&lt;td&gt;Experiments, papers, new methods&lt;/td&gt;
&lt;td&gt;PyTorch or JAX, big GPU clusters, heavy math&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ML engineer&lt;/td&gt;
&lt;td&gt;How do we train and run models reliably at scale?&lt;/td&gt;
&lt;td&gt;Training pipelines, deployment, monitoring&lt;/td&gt;
&lt;td&gt;PyTorch, Kubernetes, Spark, MLOps tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data scientist&lt;/td&gt;
&lt;td&gt;What is the data telling us?&lt;/td&gt;
&lt;td&gt;Analysis, experiments, forecasting, dashboards&lt;/td&gt;
&lt;td&gt;SQL, Python, statistics, notebooks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI engineer&lt;/td&gt;
&lt;td&gt;How do we turn a model into a product that works?&lt;/td&gt;
&lt;td&gt;LLM apps, search over documents, agents, testing, shipping&lt;/td&gt;
&lt;td&gt;Python or TypeScript, model APIs, vector search, eval tools&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What an AI engineer does in a normal week
&lt;/h2&gt;

&lt;p&gt;Here's a realistic list. Not every week has all of these, but most weeks have most of them.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Talks with a user or teammate about a problem worth solving. Sometimes the answer is "you don't need AI for this," and saying so is part of the job.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Writes code that sends a request to a model, gives it the right information, and handles whatever comes back.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Connects the model to real stuff: documents, databases, APIs, internal tools.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Builds tests (people call them &lt;em&gt;evals&lt;/em&gt;) to find out whether the thing really works. "It looked good in the demo" is not a measurement.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Chases down weird failures. The model invented a refund policy that doesn't exist. A user pasted in something strange. A tool call timed out halfway through.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Keeps an eye on cost and speed. A feature that costs too much per request, or takes forty seconds to answer, won't survive for long.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Ships it, then watches how people actually use it. This part usually teaches you the most.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice how little of that list is math. There's some, and we'll cover it. But most of the job is regular engineering with one unpredictable piece sitting in the middle.&lt;/p&gt;

&lt;h2&gt;
  
  
  What you need to know
&lt;/h2&gt;

&lt;p&gt;An honest skills list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Solid programming.&lt;/strong&gt; Mostly Python, and more and more TypeScript.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Web basics.&lt;/strong&gt; APIs, databases, async code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Enough ML to understand the models.&lt;/strong&gt; Not to invent them, just to understand why they act the way they do and why they sometimes fail.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;A testing mindset.&lt;/strong&gt; You'll be checking outputs that change from run to run, which is harder than it sounds.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Clear communication.&lt;/strong&gt; You'll often explain to non-technical people why the model did something odd.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Taste.&lt;/strong&gt; Knowing when an answer is good enough and when it isn't.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And what you &lt;strong&gt;don't&lt;/strong&gt; need to get started: a PhD, a master's degree, a GPU cluster, or math beyond the basics we'll cover together in Phase 2.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is this job for you?
&lt;/h2&gt;

&lt;p&gt;It's probably a good fit if you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;like building things and watching people use them&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;can live with messy, imperfect results (the same question can get a different answer tomorrow)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;enjoy debugging, even when the bug is "the model just decided to do something else"&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It might not be if you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;want to invent new model architectures or prove things mathematically. That's the research path, and it's a great one, just a different one.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;really dislike ambiguity. AI products are never 100% correct, and a big part of the work is deciding how wrong is too wrong.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A warning about job titles
&lt;/h2&gt;

&lt;p&gt;Titles are a mess. Don't trust them, read the job description.&lt;/p&gt;

&lt;p&gt;If it talks about &lt;em&gt;building&lt;/em&gt;, &lt;em&gt;shipping&lt;/em&gt;, &lt;em&gt;LLM apps&lt;/em&gt;, &lt;em&gt;RAG&lt;/em&gt;, &lt;em&gt;agents&lt;/em&gt;, or &lt;em&gt;evals&lt;/em&gt;, that's AI engineering. If it talks about &lt;em&gt;pretraining&lt;/em&gt;, &lt;em&gt;distributed training&lt;/em&gt;, or &lt;em&gt;novel architectures&lt;/em&gt;, that's ML or research, and it usually wants more math and often a graduate degree.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this series will do
&lt;/h2&gt;

&lt;p&gt;This is a 79-post series that takes you from zero to a working AI engineer. We start with programming in depth, because nearly everything else depends on it. Then math and data, classic machine learning, how transformers and LLMs work inside, building real LLM apps, testing them, customizing models, shipping to production, and finally the career side: portfolio, interviews, and getting hired.&lt;/p&gt;

&lt;p&gt;You don't need to read it all in order. If you already code, the next post shows you where to jump in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try this today (10 minutes)
&lt;/h2&gt;

&lt;p&gt;Open three job postings with "AI Engineer" in the title. Copy every skill and tool they mention into a note. Then circle the ones that appear in all three.&lt;/p&gt;

&lt;p&gt;Keep that note. At the end of the series we'll come back to it and see how much of it you can now tick off.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Next in the series:&lt;/strong&gt; Post 2, &lt;em&gt;The 12-Month Roadmap&lt;/em&gt;, the whole plan with a week-by-week schedule.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Published via &lt;a href="https://zyvop.com/what-is-an-ai-engineer-in-2026-pwumi?utm_source=devto&amp;amp;utm_medium=crosspost&amp;amp;utm_campaign=syndication" rel="noopener noreferrer"&gt;ZyVOP&lt;/a&gt; — Write once in Markdown, auto-backup to GitHub, and syndicate to Dev.to, Medium &amp;amp; Hashnode in 1 click.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aiengineer</category>
      <category>airoles</category>
      <category>beginners</category>
      <category>career</category>
    </item>
    <item>
      <title>Margaret Hamilton, Who Helped Put Humans on the Moon, Has Died</title>
      <dc:creator>Pradeep Kumar</dc:creator>
      <pubDate>Sat, 10 Oct 2026 09:07:51 +0000</pubDate>
      <link>https://dev.to/pradeep_kumar_bc4e7e9f7ec/margaret-hamilton-who-helped-put-humans-on-the-moon-has-died-2lk3</link>
      <guid>https://dev.to/pradeep_kumar_bc4e7e9f7ec/margaret-hamilton-who-helped-put-humans-on-the-moon-has-died-2lk3</guid>
      <description>&lt;h2&gt;
  
  
  The alarm that didn't stop the landing
&lt;/h2&gt;

&lt;p&gt;It is July 1969. The Apollo 11 lunar module is minutes from touching down, and the onboard computer starts flashing an alarm: 1202. The machine is overloaded, thanks to a problem with a hardware switch, and nobody is sure it can handle the most delicate maneuver of the mission.&lt;/p&gt;

&lt;p&gt;In another era, that might have been the end of the landing. It wasn't, because years earlier a team of MIT software engineers had built the computer to expect trouble. Its software was priority-driven: when it was swamped, it dropped the less important jobs and held on to the ones that mattered. Mission control understood what the alarm meant, trusted the software, and told the crew to keep going. Two people walked on the moon.&lt;/p&gt;

&lt;p&gt;The woman who led that software effort was Margaret Hamilton. She died on September 30, 2026, at the age of 90, MIT announced this week.&lt;/p&gt;

&lt;h2&gt;
  
  
  From weather forecasts to the moon
&lt;/h2&gt;

&lt;p&gt;Hamilton was born in Paoli, Indiana, in 1936. She studied mathematics, finishing a degree at Earlham College in 1958 with a minor in philosophy, and moved to Boston the following year when her husband began law school. She expected the job at MIT to be temporary. It was a position in the meteorology department, writing weather-prediction software for Edward Lorenz, whose later work on chaos theory her programming helped inform.&lt;/p&gt;

&lt;p&gt;From there she moved to MIT Lincoln Laboratory to work on SAGE, the country's first air defense system. That is where she grew fascinated by something almost no one was talking about yet: how to make software reliable.&lt;/p&gt;

&lt;p&gt;In 1965, just as she was planning graduate school, her husband spotted a newspaper ad. MIT's Instrumentation Lab needed people to write the flight software that would send a man to the moon. Hamilton applied and became the first programmer hired for Apollo at MIT. By 1968 she was an assistant director running the team behind the Command and Service Module software, part of an effort that involved more than 400 people.&lt;/p&gt;

&lt;h2&gt;
  
  
  The error her daughter found
&lt;/h2&gt;

&lt;p&gt;One of the most beloved stories about Hamilton's career starts with her four-year-old daughter, Lauren, playing in the command module simulator at the lab. Lauren accidentally launched a pre-launch program in the middle of a simulated flight, and the simulator crashed.&lt;/p&gt;

&lt;p&gt;Hamilton saw the risk right away. She wrote a warning into the documentation and proposed a software safeguard, but she was overruled: astronauts were far too well trained to make that mistake. Then, on Apollo 8 in 1968, Jim Lovell did exactly that, wiping out navigation data in flight. Hamilton's team got it sorted out, and her safeguard went into the software.&lt;/p&gt;

&lt;p&gt;The lesson is one engineers still carry: build systems that anticipate mistakes and recover from them, rather than assuming people and machines will always behave. Today we call it defensive programming.&lt;/p&gt;

&lt;h2&gt;
  
  
  Giving software its due
&lt;/h2&gt;

&lt;p&gt;Hamilton didn't stop at Apollo. When the Instrumentation Lab became the independent Draper Laboratory, she kept going, founding Higher Order Software in 1976 and, about ten years later, Hamilton Technologies. Both were built around her conviction that errors should be prevented by design, not caught after the fact. Her company's Universal Systems Language grew out of that philosophy.&lt;/p&gt;

&lt;p&gt;She also fought for the idea that software deserved to be treated as a real engineering discipline. By her own account, when she began using the phrase "software engineering," people thought it was a joke. Over a career that produced more than 130 publications, the joke turned into an industry.&lt;/p&gt;

&lt;p&gt;The recognition eventually arrived:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;NASA's Exceptional Space Act Award (2003)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Presidential Medal of Freedom, presented by President Obama (2016)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Computer History Museum Fellow (2017)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Intrepid Lifetime Achievement Award (2019)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Induction into the National Aviation Hall of Fame (2022)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  An icon for a new generation
&lt;/h2&gt;

&lt;p&gt;For many people, Hamilton became familiar through a single photograph: a young woman standing beside a stack of Apollo code printouts about as tall as she is. As that image spread online, she became a symbol for women in science and technology. In 2015 the Apollo software was uploaded in full to GitHub, and in 2017 she became a Lego minifigure in the Women of NASA set designed by MIT's Maia Weinstock.&lt;/p&gt;

&lt;p&gt;Looking back on her Apollo years, Hamilton said there was "no choice but to be pioneers." She was right about the pioneering, though the choice she made was her own: to take a field everyone treated as a mystery and a black box, and insist that it be done carefully, honestly, and with respect for the people who would depend on it.&lt;/p&gt;

&lt;p&gt;Every time software quietly handles a failure so you never notice, there's a bit of her work in it.&lt;/p&gt;

&lt;p&gt;Hamilton is survived by her daughter, Lauren Hamilton, her son-in-law, two grandsons, four great-grandchildren, and her siblings. A memorial service is planned for the spring in Cambridge, Massachusetts.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Source:&lt;/em&gt; &lt;a href="https://news.mit.edu/2026/margaret-hamilton-computing-pioneer-dies-1007" rel="noopener noreferrer"&gt;&lt;em&gt;MIT News, "Margaret Hamilton, computing pioneer who led software development for the Apollo program, dies at 90"&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Published via &lt;a href="https://zyvop.com/margaret-hamilton-who-helped-put-humans-on-the-moon-has-died-efpx2?utm_source=devto&amp;amp;utm_medium=crosspost&amp;amp;utm_campaign=syndication" rel="noopener noreferrer"&gt;ZyVOP&lt;/a&gt; — Write once in Markdown, auto-backup to GitHub, and syndicate to Dev.to, Medium &amp;amp; Hashnode in 1 click.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>apollo11</category>
      <category>margarethamilton</category>
      <category>mit</category>
      <category>nasa</category>
    </item>
    <item>
      <title>Google Playground: Turn a Game Idea into a Playable Game, Without Writing Code</title>
      <dc:creator>Pradeep Kumar</dc:creator>
      <pubDate>Fri, 09 Oct 2026 15:45:49 +0000</pubDate>
      <link>https://dev.to/pradeep_kumar_bc4e7e9f7ec/google-playground-turn-a-game-idea-into-a-playable-game-without-writing-code-3kf</link>
      <guid>https://dev.to/pradeep_kumar_bc4e7e9f7ec/google-playground-turn-a-game-idea-into-a-playable-game-without-writing-code-3kf</guid>
      <description>&lt;p&gt;Most people have a game idea sitting somewhere: a trivia night for friends, a tower defense with a twist, a silly racing concept from a notebook margin. Few of them get built, because making a game usually means learning an engine, a language, and a pile of tooling first.&lt;/p&gt;

&lt;p&gt;Google Labs' new experiment, &lt;strong&gt;Playground&lt;/strong&gt;, tries to remove that barrier. Launched on October 7, 2026, it lets you create playable games in minutes just by describing them in plain language.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Playground?
&lt;/h2&gt;

&lt;p&gt;Playground is a browser-based gaming platform from Google Labs, built around one promise: custom games with no coding required. You work in a chat-style interface, describe what you want, and the game takes shape. Google's tagline is "If you can think it, you can play it." Under the hood, Playground combines Google's Gemini, Nano Banana, and Lyria models with a custom system designed for game creation.&lt;/p&gt;

&lt;p&gt;Google is aiming it at two groups: hobbyists with a backlog of game ideas, and people who are simply curious about what AI can do.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works: Create, Play, Share
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Create.&lt;/strong&gt; You start in a chat window, either from a genre template (Google names Trivia, Tower Defense, and Racing) or from a blank slate. You steer the game with text prompts, and the rules are yours to set. TechCrunch also reports that you can upload your own visuals and have Playground turn them into game assets that fit the game's art style.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Play.&lt;/strong&gt; You can test the game immediately, in the browser, on a phone or a laptop. If the difficulty feels off or a mechanic falls flat, you ask for a change and try again. That play-tweak-play loop is the heart of the experience.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Share.&lt;/strong&gt; A game can stay private, go to friends by link, or be published to Playground's Explore gallery for anyone to play. You can keep modifying it after it's published. Published games go through safety screening against Google's community guidelines, and Playground supports leaderboards and multiplayer for select genres, so not every game will have them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Google Is Showcasing
&lt;/h2&gt;

&lt;p&gt;Google's launch page features a gallery of example games. A selection:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Snooze Jump&lt;/strong&gt;, a dream-themed cloud-climbing platformer with a relaxed Normal Mode and a tense Speedrun Mode.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Truth Ninja&lt;/strong&gt;, a fast-paced game that mixes reactions with trivia as you defend a temple.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Polygon Pazer&lt;/strong&gt;, a retro 3D tank battle with Mode-7-style ground, played as a tactical 3v3 team deathmatch.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Pitfalls&lt;/strong&gt;, a physics-based game about stacking skyscrapers.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Octo Pop&lt;/strong&gt;, a pixel-art arcade shooter set in a bioluminescent ocean trench, starring an octopus named Ollie.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Foosball Shootout&lt;/strong&gt;, a hypercasual foosball game.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The range is the point: a platformer, a trivia game, an arcade shooter, a physics stacker, and a sports game, each with its own look and tone. Several descriptions also mention specific design choices, like Snooze Jump's two difficulty modes, which suggests creators are shaping mechanics rather than only picking templates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;Game creation has always had a steep entry cost. Playground starts from the idea instead and lets the tooling catch up. That changes who gets to be a game designer: someone with a clever rule set or a funny theme, but no programming background, can now prototype it.&lt;/p&gt;

&lt;p&gt;The process is also conversational. You can tweak a game's physics, rewrite its rules, change its characters, or overhaul its environment just by asking, then test the result right away. That doesn't replace the whole development process, but it takes over much of the initial coding and iteration work. Cheap experiments tend to produce surprising ideas, and this makes experiments very cheap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It: A Starter Prompt
&lt;/h2&gt;

&lt;p&gt;If you have access, start specific. Here's an illustrative prompt (my own example, not one from Google):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A cozy tower defense game where garden gnomes defend a vegetable patch from crows. Three gnome types, a watercolor art style, and a boss crow every fifth wave.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;From there, change one thing at a time: slower crows, a new gnome, a harder boss. Test after each change so you can tell what helped.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things to Keep in Mind
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;It's an experiment.&lt;/strong&gt; It lives under Google Labs, so expect it to change and expect some rough edges.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Access is limited.&lt;/strong&gt; Playground launched in the U.S. for users 18 and older, and Google plans to expand to more countries. Browsing and playing games is free. Creation access is tiered by Google One membership, with free users getting a limited weekly allowance and subscribers getting higher limits.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Results will vary.&lt;/strong&gt; Describing a game gets you a playable starting point quickly, but a polished one will likely take several rounds of refinement.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Bottom Line
&lt;/h2&gt;

&lt;p&gt;Playground won't replace traditional game development, but it doesn't need to. Its value is in shrinking the distance between "I have an idea" and "I can play it" from a long learning curve to a few minutes of chatting. If you've ever had a game concept you never built, it's worth a look as soon as it's available where you are.&lt;/p&gt;

&lt;p&gt;Try it at &lt;a href="https://playground.google" rel="noopener noreferrer"&gt;&lt;strong&gt;playground.google&lt;/strong&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Google, &lt;a href="https://blog.google/innovation-and-ai/technology/ai/playground-experimental-gaming-platform/" rel="noopener noreferrer"&gt;Introducing Playground: Create and play custom games&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;TechCrunch, &lt;a href="https://techcrunch.com/2026/10/07/google-experiments-with-an-ai-powered-gaming-platform/" rel="noopener noreferrer"&gt;Google experiments with an AI-powered gaming platform&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://labs.google/playground" rel="noopener noreferrer"&gt;Google Labs: Playground&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Published via &lt;a href="https://zyvop.com/google-playground-turn-a-game-idea-into-a-playable-game-without-writing-code-npg27?utm_source=devto&amp;amp;utm_medium=crosspost&amp;amp;utm_campaign=syndication" rel="noopener noreferrer"&gt;ZyVOP&lt;/a&gt; — Write once in Markdown, auto-backup to GitHub, and syndicate to Dev.to, Medium &amp;amp; Hashnode in 1 click.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>aigamedevelopment</category>
      <category>generativeai</category>
      <category>googlelabs</category>
      <category>googleplayground</category>
    </item>
  </channel>
</rss>
