<?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>Forem: David Moran</title>
    <description>The latest articles on Forem by David Moran (@david_moran_0a44206d28c04).</description>
    <link>https://forem.com/david_moran_0a44206d28c04</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3677131%2F619699b0-4fe3-4aa0-999f-b699bd503998.jpg</url>
      <title>Forem: David Moran</title>
      <link>https://forem.com/david_moran_0a44206d28c04</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/david_moran_0a44206d28c04"/>
    <language>en</language>
    <item>
      <title>pytest Features That Changed How I Write Tests</title>
      <dc:creator>David Moran</dc:creator>
      <pubDate>Wed, 28 Jan 2026 13:00:00 +0000</pubDate>
      <link>https://forem.com/david_moran_0a44206d28c04/pytest-fixtures-changed-how-i-write-tests-1503</link>
      <guid>https://forem.com/david_moran_0a44206d28c04/pytest-fixtures-changed-how-i-write-tests-1503</guid>
      <description>&lt;p&gt;This week for my &lt;a href="https://github.com/DaveMoran/AST-Analyzer" rel="noopener noreferrer"&gt;AST Analyzer project&lt;/a&gt; I decided to do a deep dive on the testing framework &lt;code&gt;pytest&lt;/code&gt;. When i started off the week I genuinely thought I already knew everything I needed, but man was I pleasantly surprised at how powerful that testing suite is. Today I'm going to go over a few new things I learned when implementing my tests suite, including:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Assertion behind the scenes&lt;/li&gt;
&lt;li&gt;Fixtures&lt;/li&gt;
&lt;li&gt;Capsys and Caplog&lt;/li&gt;
&lt;li&gt;Parametrization&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Like last week's post, most of these were learned with the combination of two resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://learning.oreilly.com/library/view/python-testing-with/9781680509427/" rel="noopener noreferrer"&gt;Python Testing with pytest - Brian Okken&lt;/a&gt; (ch 1 - 5)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://learning.oreilly.com/library/view/python-cookbook-3rd/9781449357337/" rel="noopener noreferrer"&gt;Python Cookbook, 3rd Edition - David Beazley, Brian K. Jones&lt;/a&gt; (ch 14)&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Assertion
&lt;/h2&gt;

&lt;p&gt;One of the first things I noticed when switching from unittest to pytest was how much simpler assertions are. In unittest, you need to use specific methods like &lt;code&gt;assertEqual&lt;/code&gt;, &lt;code&gt;assertTrue&lt;/code&gt;, &lt;code&gt;assertIn&lt;/code&gt;, etc:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# unittest style
self.assertEqual(result, 5)
self.assertTrue(is_valid)
self.assertIn("error", message)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With pytest, you just use Python's built-in &lt;code&gt;assert&lt;/code&gt; statement:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# pytest style
assert result == 5
assert is_valid
assert "error" in message
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I was wondering how a 3rd party package could make assertion statements clearer than what's built in to Python, and while digging deeper I found that there's a whole process that goes on when the test fails.&lt;/p&gt;

&lt;h3&gt;
  
  
  Assert Rewriting with AST
&lt;/h3&gt;

&lt;p&gt;Pytest uses the built in assert function because it raises an AssertionException when the case does not pass. This built-in assertion check allows Pytest to work with built-in functionality and expand functionality from there.&lt;/p&gt;

&lt;p&gt;For example, once an AssertionException is raised pytest uses assertion rewriting to replace the default string. This is done at import time where pytest can grab the value of each variable and sub expressions. This new info is used to provide the logs with more details like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Provides a diff of what was given and what was expected&lt;/li&gt;
&lt;li&gt;The line the test failed at&lt;/li&gt;
&lt;li&gt;What was the failure (mismatch, missing item, etc)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;By doing this at import time, it's able to place all code in an Abstract Syntax Tree (yes, the same AST we're working with in this project), finds all the &lt;code&gt;assert&lt;/code&gt; statements, and rewrites them to capture intermediate values before the assertion runs. This is why when an assertion fails, pytest can show you exactly what each part of the expression evaluated to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def test_string_comparison():
    result = "hello world"
    assert result == "hello pytest"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;=========================== FAILURES ===========================
_________________ test_string_comparison _______________________

    def test_string_comparison():
        result = "hello world"
&amp;gt;       assert result == "hello pytest"
E       AssertionError: assert 'hello world' == 'hello pytest'
E         
E         - hello pytest
E         + hello world

======================== 1 failed in 0.02s =====================
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This works for complex expressions too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def test_list_membership():
    items = ["apple", "banana", "cherry"]
    target = "grape"
    assert target in items
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;E       AssertionError: assert 'grape' in ['apple', 'banana', 'cherry']
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pytest captured both the value of &lt;code&gt;target&lt;/code&gt; and &lt;code&gt;items&lt;/code&gt; before the assertion ran, so it can show you exactly why it failed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixtures
&lt;/h2&gt;

&lt;p&gt;Coming from Node, one thing I appreciated about jest is being able to use &lt;code&gt;beforeEach&lt;/code&gt;, &lt;code&gt;beforeAll&lt;/code&gt;, &lt;code&gt;afterEach&lt;/code&gt;, and &lt;code&gt;afterAll&lt;/code&gt; in combination with &lt;code&gt;describe&lt;/code&gt; scopes to setup and tear down test data in a straightforward manner. I was relieved to see that pytest gives us these features with the ability of fixtures that allow us to specify the same level of setup, teardown, and scope that's in jest.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Fixtures over beforeEach
&lt;/h3&gt;

&lt;p&gt;While I was relieved to see familiar patterns, I quickly realized that fixtures are actually more powerful than Jest's approach in a few key ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Composition&lt;/strong&gt; - Fixtures can depend on other fixtures. We injected pytest's built-in tmp_path fixture into our own custom fixture - that's composition in action. In Jest, you'd have to nest your beforeEach blocks or manually call setup functions to achieve something similar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scoping&lt;/strong&gt; - Fixtures let you specify how often they run: function (default, runs for each test), class, module, or session. If I have an expensive setup like connecting to a database, I can scope it to session and it only runs once for the entire suite.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reusability via conftest.py&lt;/strong&gt; - Any fixture defined in a conftest.py file is automatically available to all tests in that directory and subdirectories. No imports needed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pay for what you use&lt;/strong&gt; - This one was subtle but important. With Jest's beforeEach, the setup runs before every test in that scope whether you need it or not. With pytest, fixtures only run when a test actually requests them as a parameter. If I have 10 tests in a class but only 3 need the sample_code_file fixture, it only gets created 3 times. This keeps tests fast and avoids unnecessary setup.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Setup and Teardown
&lt;/h3&gt;

&lt;p&gt;Part of testing the ASTAnalyzer was making sure that we had a file with data to parse throughout out tests. Initially I created a sample file in a tests/data document and tested against that, but then found that we can create one using a combination of our own fixture and one of pytest's built in fixtures:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@pytest.fixture
def sample_code_file(tmp_path):
    """Factory fixture to create temporary Python files with specified content."""

    def _create_file(content, filename="test_file.py"):
        file_path = tmp_path / filename
        file_path.write_text(content)
        return str(file_path)

    return _create_file

def test_enter_opens_file(self, sample_code_file):
    """__enter__ opens the file and returns file object."""
    filepath = sample_code_file("x = 1")
    with Parser(filepath) as f:
        assert f is not None
        assert not f.closed

def test_context_manager_with_exception(self, sample_code_file):
    """File is closed even when exception occurs."""
    filepath = sample_code_file("content")
    file_ref = None
    with pytest.raises(ValueError):
        with Parser(filepath) as f:
            file_ref = f
            raise ValueError("test error")
    assert file_ref.closed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we can see, we've created a &lt;code&gt;sample_code_file&lt;/code&gt; that takes in the content that we want to test against and automatically writes it to a file. This allows us to have a piece of reusable code that can be used to test all types of content inside of the file&lt;/p&gt;

&lt;h2&gt;
  
  
  Capsys and Caplog
&lt;/h2&gt;

&lt;p&gt;One of the harder things I found while setting up my initial tests was making sure that my printing and logging decorators were being tested. Initially I was using &lt;code&gt;unittest.patch&lt;/code&gt; to mimic this behavior like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@logger(logging.DEBUG)
def add(a, b):
    return a + b


def test_ast_log_defaults():
    with patch("ast_analyzer.decorators.logger.logger") as mock_logger:
        add(3, 5)

    mock_logger.debug.assert_called()
    call_args = str(mock_logger.debug.call_args)
    assert "add" in call_args
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The test technically works, it checks that mock logger was called in the &lt;code&gt;add()&lt;/code&gt; function, but it doesn't actually check what the contents of that log are. With pytest we can use &lt;code&gt;caplog&lt;/code&gt; to grab the output and save it to a file that we can then read from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@logger(logging.DEBUG)
def add(a, b):
    return a + b

def test_ast_log_defaults(caplog):
    with caplog.at_level(logging.DEBUG):
        add(3, 5)

    assert "DEBUG" in caplog.text
    assert "add" in caplog.text
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this new functionality, we can look for specific strings of text inside of the log. We also have control over the logging level that we display, so we can write tests for DEBUG and INFO to check that the text in both of those logs are appearing properly. This can also be done with capsys for checking print statements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def test_prints_timing_output(capsys):
    """Decorator should print timing information to stdout."""

    factorial(3)
    captured = capsys.readouterr()
    assert "factorial" in captured.out
    assert "-&amp;gt;" in captured.out
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In both formats we can see the ease of use that pytest gives us for accessing this text&lt;/p&gt;

&lt;h2&gt;
  
  
  Parametrization
&lt;/h2&gt;

&lt;p&gt;Last but not least, there's parametrization. Parametrization is a technique that's used in python to make code more modular and reusable. In our specific case, we use parametrization for testing by iterating through multiple parameters. This is a great way to reduce the amount of code in your test suite without losing any functionality. To demo this, I'll show a before and after of a series of tests made in the repo.&lt;/p&gt;

&lt;h3&gt;
  
  
  Before Parametrization
&lt;/h3&gt;

&lt;p&gt;When we create our custom &lt;code&gt;ASTNode&lt;/code&gt;, one thing we do on initialization is see how many children the Node has. In order to test that our &lt;code&gt;__init__&lt;/code&gt; declaration for children is working, we set up the following tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def test_str_shows_children_count_one_child(self):
    """__str__ displays the number of children."""
    tree = ast.parse("x = 1")
    node = ASTNode(tree)
    assert str(node) == f"AST Node | Children: 1"

def test_str_shows_children_count_mult_children(self):
    """__str__ displays the number of children."""
    tree = ast.parse("x = 1\ny = 2\nz = 3")
    node = ASTNode(tree)
    assert str(node) == f"AST Node | Children: 3"

---

================ test session starts ================
platform darwin -- Python 3.12.12, pytest-9.0.2, pluggy-1.6.0
rootdir: /Users/davidmoran/Sites/ai-bootcamp/projects/AST-Analyzer
configfile: pyproject.toml
plugins: cov-7.0.0
collected 2 items                                   

tests/test_astnode.py ..                      [100%]

================= 2 passed in 0.03s =================
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, the code is simple enough where having it be exactly the same isnt too bad, but its repetitive which is something that we want to avoid. When playing around with this I thought of combining this into a collection and testing that way:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def test_str_shows_children_count(self):
    """__str__ displays the number of children."""
    tree = ast.parse("x = 1")
    node = ASTNode(tree)
    assert str(node) == "AST Node | Children: 1"

    tree = ast.parse("x = 1\ny = 2\nz = 3")
    node = ASTNode(tree)
    assert str(node) == "AST Node | Children: 3"

---

================ test session starts =================
platform darwin -- Python 3.12.12, pytest-9.0.2, pluggy-1.6.0
rootdir: /Users/davidmoran/Sites/ai-bootcamp/projects/AST-Analyzer
configfile: pyproject.toml
plugins: cov-7.0.0
collected 1 item                                     

tests/test_astnode.py .                        [100%]

================= 1 passed in 0.02s ==================
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While this is definitely a lot cleaner and lean, one thing I didn't appreciate is that I'm stuffing two test cases into one. In the future if one of these were to fail (in a larger test) it would be a bit annoying trying to figure out which one of these was the culprit&lt;/p&gt;

&lt;h3&gt;
  
  
  Introducing: Parametrization
&lt;/h3&gt;

&lt;p&gt;Parametrization solves the above by creating a matrix of key values to test against and running them against 1 test. Unlike our second option above, using parametrization breaks the singular test out into the number of suites we specified above so that we can see which of the items failed a test&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@pytest.mark.parametrize(
    "code,expected_count",
    [
        ("x = 1", 1),
        ("x = 1\ny = 2\nz = 3", 3),
    ],
)
def test_str_shows_children_count(self, code, expected_count):
    """__str__ displays the number of children."""
    tree = ast.parse(code)
    node = ASTNode(tree)
    assert str(node) == f"AST Node | Children: {expected_count}"

---

================ test session starts =================
platform darwin -- Python 3.12.12, pytest-9.0.2, pluggy-1.6.0
rootdir: /Users/davidmoran/Sites/ai-bootcamp/projects/AST-Analyzer
configfile: pyproject.toml
plugins: cov-7.0.0
collected 2 items                                    

tests/test_astnode.py ..                       [100%]

================= 2 passed in 0.05s ==================
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The way this works is pretty straightforward:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;We use the &lt;code&gt;@pytest.mark.parametrize&lt;/code&gt; and pass in two arguments

&lt;ul&gt;
&lt;li&gt;A string of variable names separated by commas&lt;/li&gt;
&lt;li&gt;A tuple of values that you want each variable to represent on iteration&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;For each tuple in our collection we passed, the test will iterate over and replace the variables with the values we provided&lt;/li&gt;
&lt;li&gt;Every time the test is run, it reports it as a separate test, meaning that we can get more insight into which parameter will fail in case of an error.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This feature of pytest works great when you want to test one specific thing against a number of start points. For our test, we just wanted to make sure that an ASTNode was created successfully based on the input, so it's a perfect candidate for parametrization. If I wanted to check the outputs or error handling of certain inputs, that is better handled as a separate test so that we can check on a number of items (type of exception if raised, state of ASTNode, log statements, etc).&lt;/p&gt;

</description>
      <category>python</category>
      <category>testing</category>
      <category>pytest</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Python Closures: Coming from JavaScript</title>
      <dc:creator>David Moran</dc:creator>
      <pubDate>Tue, 20 Jan 2026 13:00:00 +0000</pubDate>
      <link>https://forem.com/david_moran_0a44206d28c04/python-closures-coming-from-javascript-5885</link>
      <guid>https://forem.com/david_moran_0a44206d28c04/python-closures-coming-from-javascript-5885</guid>
      <description>&lt;p&gt;Every time I want to learn something new I always end up doing it the old school way: picking up a book. Sure, in today’s age most of the up-to-date information can be found in a mix of documentation and Udemy courses, but there’s something about picking up a piece of text (digital or hardbound) that makes me think that the knowledge there is meant to last much longer than what’s shown in a video course. I’ve always seen it as:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Books – Gain depth of knowledge on a topic&lt;/li&gt;
&lt;li&gt;Videos – Quickly ramp up to use a specific technology&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That being said, one of my goals for this year is to up my Python skills so that I can be more prepared to build out items for AI/ML use cases. Part of this learning process has been getting to understand Closures in Python. At first I just saw these as a function with a hidden variable (which is technically true), but after doing some digging into what they do and how their scopes are defined I got to learn a bit more about the Python language.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A quick heads up, most of this knowledge was gained from reading &lt;a href="https://learning.oreilly.com/library/view/fluent-python-2nd/9781492056348/" rel="noopener noreferrer"&gt;Fluent Python, 2nd Ed&lt;/a&gt; and &lt;a href="https://learning.oreilly.com/library/view/python-cookbook-3rd/9781449357337/" rel="noopener noreferrer"&gt;Python Cookbook, 3rd Ed&lt;/a&gt;. If you haven’t read them already I highly recommend as they provide an in-depth explanation of Python best practices while not being too boring like a text book&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Why Closures Matter
&lt;/h2&gt;

&lt;p&gt;Before we dive under the hood, here’s a quick rundown on why closures are an important part of Python to understand early on:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Decorators&lt;/strong&gt; – Python decorators are built on closures (more on this in my next post)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Callbacks&lt;/strong&gt; – Similar to JavaScript callbacks, closures let you “remember” state&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Factory functions&lt;/strong&gt; – Create specialized functions on the fly (like our multiplier example)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data encapsulation&lt;/strong&gt; – Hide internal state without classes&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you’ve used decorators like &lt;code&gt;@app.route()&lt;/code&gt; in Flask, you’ve used closures—even if you didn’t realize it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is a Closure?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Closure&lt;/strong&gt;: a nested function that remembers and accesses variables from its enclosing (outer) function’s scope, even after the outer function has finished executing, allowing the inner function to maintain state&lt;/p&gt;

&lt;p&gt;Looking at the definition above, you can see where I was half correct with my initial assumption. Broken down in code, we can take a look at a function like. Using blueprint code, we can start defining our closure like so:&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Basic Example: Multiplier&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Example 1: mult_closure.py: A closure to make a multiplier out of any function&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def make_multiplier_of(n):
    """Outer function that takes a factor 'n' and returns a closure."""

    def multiplier(x):
        """Inner function (closure) that uses the remembered factor 'n'."""
        return x * n

    return multiplier


"""Create two closures, each remembering a different 'n' value"""
doubler = make_multiplier_of(2)  # Remembers n=2
tripler = make_multiplier_of(3)  # Remembers n=3

"""Results. Note that we no longer need to pass a scalar as the closure will remember the initial value it was passed"""
print(f"8 times 2 is {doubler(8)}")
print(f"4 times 3 is {tripler(4)}")

-----

8 times 2 is 16
4 times 3 is 12
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we can see above, we’ve created a new closure that is able to create a multiplier dynamically by saving one of the scalars to memory. This allows developers to only pass through the number they want to see multiplied by instead of always passing in the scalar&lt;/p&gt;

&lt;p&gt;Behind the scenes, we’re returning the nested function (the closure) and allowing it to be run right after the multiplier creator is called. Another way to visualize this is side by side:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"""Saved to a variable"""
doubler = make_multiplier_of(2)
doubler(4)

"""Executed in-place"""
make_multiplier_of(2)(4)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  JavaScript Equivalent
&lt;/h2&gt;

&lt;p&gt;Coming from JS I was curious to see if there’s an equivalent structure to this. Turns out there is!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example 2. mult_closure.js: The same multiplier closure from above but in JS&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function makeMultiplierOf(n) {
    // Outer function that takes a factor 'n' and returns a closure

    function multiplier(x) {
        // Inner function (closure) that uses the remembered factor 'n'
        return x * n;
    }

    return multiplier;
}

// Instances of our new closure
const doubler = makeMultiplierOf(2);
const tripler = makeMultiplierOf(3);

// Results - the closure remembers the initial value it was passed
console.log(`8 times 2 is ${doubler(8)}`);
console.log(`4 times 3 is ${tripler(4)}`);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A Real-Life Implementation
&lt;/h2&gt;

&lt;p&gt;I mentioned before that one of the things a closure can do is maintain state across calls. This can be better visualized if we built a series of log lists:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example 3. logger_closure.py&lt;/strong&gt;: Saving log messages to lists in the outer function&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def create_logger(source):
    """Outer function now contains a list of logs"""
    logs = []

    def log_message(message=None):
        """
        Inner function, holds the logic that will be run during the call to the outer function.
        We'll always return the logs, and only append when a message is provided
        """
        if message:
            logs.append({"source": source, "message": message})
        return logs

    return log_message


error_log = create_logger("error")
info_log = create_logger("info")

info_log("Hello world")
error_log("File Not Found")

print(error_log("Zero Division"))
print(info_log())

-----

[{'source': 'error', 'message': 'File Not Found'}, {'source': 'error', 'message': 'Zero Division'}]
[{'source': 'info', 'message': 'Hello world'}]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this iteration, we can now see that we have a new variable &lt;code&gt;logs&lt;/code&gt; that is a list being saved in the outside function. This &lt;code&gt;logs&lt;/code&gt; variable is saved to the scope of each creation of the closer, meaning that even though &lt;code&gt;info_log&lt;/code&gt; and &lt;code&gt;error_log&lt;/code&gt; both contain the &lt;code&gt;list&lt;/code&gt; variable, they’re maintaining their own unique version of it (as seen in the print statements)&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding Scope: The LEGB Rule
&lt;/h2&gt;

&lt;p&gt;Python’s way of dealing with variables is different than JS. Thing pre-ES6 days where we only had the &lt;code&gt;var&lt;/code&gt; keyword and we had to be real careful with how we declared our variables. Nowadays those things are resolved by &lt;code&gt;let&lt;/code&gt; and &lt;code&gt;const&lt;/code&gt;, but Python does not have an equivalent. Instead, it assumes you know the LEGB rule and are applying it with your code. For a quick refresher:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example 4. legb_overview.py: Visual representation of the LEGB rule&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"""Global scope"""
x = "global"          

def outer():
    """Enclosing scope"""
    y = "enclosing"   

    def inner():
        """Local scope"""
        z = "local"   # Local scope

        """Python searches: Local → Enclosing → Global → Built-in"""
        print(z)      # Found in Local
        print(y)      # Not in Local, found in Enclosing
        print(x)      # Not in Local/Enclosing, found in Global
        print(len)    # Not in Local/Enclosing/Global, found in Built-in

    inner()

outer()

----- 
local
enclosing
global
&amp;lt;built-in function len&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;em&gt;Local&lt;/em&gt; – The function checks the local scope of the function to see if the variable was determined there&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Enclosing&lt;/em&gt; – This is for those hidden variables we talked about with closures. If the interpreter notices there’s a nested function, then it will check one level above to see if the variable was defined there&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Global&lt;/em&gt; – At first you might think, oh this is the rest of the file and widest scope, which is only half true. The first part is correct, it will check for any globally defined variables (either at the topmost scope of an import or outside of any function definitions). However, there’s actually one last step that is checked&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Built-in&lt;/em&gt; – This is for the protected keywords, Python’s interpreter will check if the variable you’re trying to call is actually one that comes out of the box (like &lt;code&gt;len()&lt;/code&gt;) and will try to call that&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;Important: Python stops searching as soon as it finds the variable in any scope. It doesn’t keep looking in outer scopes once it finds a match.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;The &lt;code&gt;nonlocal&lt;/code&gt; Keyword&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Example 5. make_avg_err.py: An example closure without a nonlocal variable&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"""Before"""
def make_avg():
    count = 0
    total = 0

    def inner(new_val):
        count += 1
        total += new_val
        return total / count

    return inner


avg = make_avg()
print(avg(10))

-----

UnboundLocalError: cannot access local variable 'count' where it is not associated with a value
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is where the &lt;code&gt;nonlocal&lt;/code&gt; keyword comes into play. If you try to change a variable from the outer scope (e.g., count = count + 1), Python will freak out and say “I don’t know what count is!” (It thinks you are creating a new local variable).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Example 6. make_avg_fixed.py: Now using nonlocal to properly scope variables&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"""After"""
def make_avg():
    count = 0
    total = 0

    def inner(new_val):
        nonlocal count, total
        count += 1
        total += new_val
        return total / count

    return inner


avg = make_avg()
print(avg(10))

-----
10.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, this is only to be used when we’re dealing with immutable variables, like &lt;code&gt;int&lt;/code&gt;, &lt;code&gt;float&lt;/code&gt;, &lt;code&gt;str&lt;/code&gt;, etc. Our example above with &lt;code&gt;list&lt;/code&gt; does not need a &lt;code&gt;nonlocal&lt;/code&gt; keyword because you’re not changing the variable, just the contents of it. &lt;code&gt;list.append(item)&lt;/code&gt; simply adds a new item to the existing list, whereas &lt;code&gt;counter += 1&lt;/code&gt; is taking a new &lt;code&gt;int&lt;/code&gt; and replacing the value of &lt;code&gt;counter&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap Up
&lt;/h2&gt;

&lt;p&gt;Coming from a JS background it’s really easy to try and just do the same things in a diff language without understanding the nuances of the new code you’re writing. I’ve been building out this &lt;a href="https://github.com/DaveMoran/AST-Analyzer" rel="noopener noreferrer"&gt;AST File Parser&lt;/a&gt; to go through all of the small things that make Python efficient. I’m hoping to wrap this project up by the end of the month so that I can be prepared for some fun MCP projects. Until then, will be happy to share some other findings in Python like Generators, Decorators, Special Methods, etc.&lt;/p&gt;

</description>
      <category>python</category>
      <category>javascript</category>
      <category>beginners</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
