<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Andrew Wegner | Ponderings of an Andy - Technical Solutions</title><link href="https://andrewwegner.com/" rel="alternate"/><link href="https://andrewwegner.com/feeds/technical-solutions.atom.xml" rel="self"/><id>https://andrewwegner.com/</id><updated>2025-07-22T09:00:00-05:00</updated><subtitle>Can that be automated?</subtitle><entry><title>Python Gotcha: Reusing Generators Returns Nothing</title><link href="https://andrewwegner.com/python-gotcha-reusing-generator-returns-nothing.html" rel="alternate"/><published>2025-07-22T09:00:00-05:00</published><updated>2025-07-22T09:00:00-05:00</updated><author><name>Andy Wegner</name></author><id>tag:andrewwegner.com,2025-07-22:/python-gotcha-reusing-generator-returns-nothing.html</id><summary type="html">&lt;p&gt;Generators provide lazy evaluation for processing large datasets efficiently. However, once a generator is exhausted through iteration, it cannot be reused or reset. Let's cover this common gotcha that trips up developers new to this Python feature.&lt;/p&gt;</summary><content type="html">
&lt;h2 id="introduction"&gt;Introduction&lt;a class="headerlink" href="#introduction" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In the previous article, we looked at &lt;a href="https://andrewwegner.com/python-gotcha-logging-uncaught-exception.html"&gt;logging uncaught exceptions&lt;/a&gt;. Let's utilize the log output from that post for another common task: error log file processing. This example is going to be pretty simple, as the error log for the post is tiny, but in a production environment this could be hundreds of thousands of lines, or gigabytes in size. Potentially, that's a lot to shove into memory for processing. But that's where a generator can come in to help.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://docs.python.org/3/tutorial/classes.html#generators"&gt;Generators&lt;/a&gt; do not store their results, instead they maintain state and &lt;code&gt;yield&lt;/code&gt; the result back to the caller. This means each line in a log can be processed and returned, without loading the entire file. &lt;/p&gt;
&lt;h3 id="flashback"&gt;Flashback&lt;a class="headerlink" href="#flashback" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;As a reminder from the &lt;a href="https://andrewwegner.com/python-gotcha-logging-uncaught-exception.html"&gt;previous article&lt;/a&gt;, the log file being used looks like this:&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="mf"&gt;2025&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;07&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;14&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;22&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;30&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;44&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;061&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__main__&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;
&lt;span class="mf"&gt;2025&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;07&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;14&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;22&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;30&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;44&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;061&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__main__&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CRITICAL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uncaught&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;will&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;terminate&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;Traceback&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;most&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;recent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"/home/andy/main.py"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;~~~~&lt;/span&gt;&lt;span class="o"&gt;^^&lt;/span&gt;
&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"/home/andy/main.py"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;27&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;log&lt;/span&gt;&lt;span class="n"&gt;ger&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;divide&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="w"&gt;                &lt;/span&gt;&lt;span class="err"&gt;~~~~~~&lt;/span&gt;&lt;span class="o"&gt;^^^^^&lt;/span&gt;
&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"/home/andy/main.py"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;divide&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;return&lt;/span&gt;&lt;span class="w"&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="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;~&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="err"&gt;~&lt;/span&gt;
&lt;span class="n"&gt;ZeroDivisionError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;division&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;zero&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;There is one &lt;code&gt;INFO&lt;/code&gt; entry and one &lt;code&gt;CRITICAL&lt;/code&gt; entry.&lt;/p&gt;
&lt;h2 id="yield-vs-return"&gt;yield vs return&lt;a class="headerlink" href="#yield-vs-return" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It's important to understand what makes a generator different from a regular function. The key distinction is the &lt;code&gt;yield&lt;/code&gt; keyword. When a function contains &lt;code&gt;yield&lt;/code&gt;, Python treats it as a generator function, which behaves differently from functions that use &lt;code&gt;return&lt;/code&gt;. &lt;/p&gt;
&lt;p&gt;A regular function with &lt;code&gt;return&lt;/code&gt; executes completely, returns back a single result and then terminates. A function with &lt;code&gt;yield&lt;/code&gt; creates a generator object that can pause execution, return a value, and later resume from exactly where it left off. This is what enables the memory-efficient and lazy evaluation that makes generators powerful.&lt;/p&gt;
&lt;h2 id="gotcha"&gt;Gotcha&lt;a class="headerlink" href="#gotcha" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="log-processing"&gt;Log processing&lt;a class="headerlink" href="#log-processing" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;A generator result can only be utilized one time. Whether you are using a generator to output the next item in a sequence or process a file line by line, once you have passed an iterable or exhausted the generator, it doesn't get reused. This simple generator demonstrates the issue.&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;def read_log_lines(filename):
    with open(filename, 'r') as f:
        for line in f:
            if 'CRITICAL' in line:
                yield line.strip()

error_logs = read_log_lines('app.log')

error_count = len(list(error_logs))
print(f"Found {error_count} CRITICAL lines")

recent_errors = [log for log in error_logs if '2025' in log]
print(f"Recent errors: {len(recent_errors)}")
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;At first glance, it looks like this will read the log file, count the number of errors and then output how many of those were in 2025 (or contain the string &lt;code&gt;2025&lt;/code&gt;). However, the actual output is different.&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Found 1 CRITICAL lines
Recent errors: 0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;code&gt;error_logs&lt;/code&gt; is a generator object. If a function &lt;code&gt;yield&lt;/code&gt;s, it is a generator. As &lt;code&gt;error_count&lt;/code&gt; is initialized, it processes the error log and yields back any critical lines. The &lt;code&gt;list()&lt;/code&gt; function will consume the entire generator (file). A few lines later, the developer wants to see how many of these are recent errors and attempts to go through the &lt;code&gt;error_logs&lt;/code&gt; generator again. Success! No recent errors!&lt;/p&gt;
&lt;p&gt;Right?&lt;/p&gt;
&lt;p&gt;No, and looking at the log quickly shows that.&lt;/p&gt;
&lt;h3 id="fibonacci"&gt;Fibonacci&lt;a class="headerlink" href="#fibonacci" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Let's use a generator to build the Fibonocci sequence. Spoiler for interviews! In this case, I'm going to use a generator to get the first 10 items. Then print out the first 5 and then try to print the entire list of 10 items.&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fibonacci&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="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;range&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="w"&gt;        &lt;/span&gt;&lt;span class="nb"&gt;yield&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;

&lt;span class="n"&gt;fib_numbers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fibonacci&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="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"First 5 numbers:"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fib_numbers&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Entire List:"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;full_list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fib_numbers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;full_list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The output for this is:&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;First 5 numbers:
0
1
1
2
3
Entire List:
[5, 8, 13, 21, 34]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Notice that the &lt;code&gt;full_list&lt;/code&gt; variable only contains the items remaining on the generator. Since the first 5 (indexes &lt;code&gt;0&lt;/code&gt; through &lt;code&gt;4&lt;/code&gt;) were printed, they are no longer part of the generator. When the full list is printed, only the remaining items can be printed.&lt;/p&gt;
&lt;h2 id="solution"&gt;Solution&lt;a class="headerlink" href="#solution" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The solution to the problem is easy enough. Call the generator function again. For example, with the log code from above:&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;error_logs = read_log_lines('app.log')
error_count = len(list(error_logs))
...
error_logs = read_log_lines('app.log')  # Call again and create a new generator
recent_errors = [log for log in error_logs if '2025' in log]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;For the Fibonacci code, you would call &lt;code&gt;fib_numbers = fibonacci(10)&lt;/code&gt; again before printing the full list.&lt;/p&gt;
&lt;p&gt;Obviously, there is a down side here with duplicate processing of the same data due to running the generator twice. This could probably be solved with some logic adjustments to the generator or the code calling the generator, but that'll vary by application depending on what the generator is doing.&lt;/p&gt;
&lt;h3 id="conclusion"&gt;Conclusion&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The important thing to take away from this is that once you have iterated over an item in a generator, it's no longer part of a generator. This means that if you want to get clever and see if there are more items in a generator, or determine the next item, you've consumed the next item.&lt;/p&gt;
&lt;p&gt;The power of generators, especially when processing large amounts of data, can't be understated. But, at the same time, it's important to know that reusing an exhausted generator or attempting to access a previous item directly from the generator is not going to work. Instead, to reuse generator logic, call the generator function again to create a new generator object, or convert to a list if memory permits and multiple iterations are needed.&lt;/p&gt;</content><category term="Technical Solutions"/><category term="technical"/></entry><entry><title>Python Gotcha: Logging an uncaught exception</title><link href="https://andrewwegner.com/python-gotcha-logging-uncaught-exception.html" rel="alternate"/><published>2025-07-14T23:00:00-05:00</published><updated>2025-07-14T23:00:00-05:00</updated><author><name>Andy Wegner</name></author><id>tag:andrewwegner.com,2025-07-14:/python-gotcha-logging-uncaught-exception.html</id><summary type="html">&lt;p&gt;Uncaught exceptions will crash an application. If you don't know how to log these, it can be difficult to troubleshoot such a crash. Let's walk through this gotcha and see how to fix it.&lt;/p&gt;</summary><content type="html">
&lt;h2 id="introduction"&gt;Introduction&lt;a class="headerlink" href="#introduction" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A well build application will use logging instead of &lt;code&gt;print&lt;/code&gt; statements. An exceptionally well built one will log in such a way that additional context is added to each log message and be consumable by a log aggregation service. Perhaps I'll write up such an article in the future. For now though, let's focus on a single problem. &lt;/p&gt;
&lt;p&gt;Here is some sample code to demonstrate the problem.&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;logging&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setLevel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FileHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"app.log"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;formatter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Formatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;%(asctime)s&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;%(name)s&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;%(levelname)s&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;%(message)s&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setFormatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;formatter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;divide&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="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="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Application start"&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="mi"&gt;10&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;divide&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;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Application end"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"__main__"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&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;Briefly, this sets up the &lt;code&gt;app.log&lt;/code&gt; file to receive our log messages. The &lt;code&gt;main&lt;/code&gt; function is going to divide two numbers, log the result, and end the program. Pretty simple.&lt;/p&gt;
&lt;p&gt;Except, in this case, it is dividing by zero. This throws an error and crashes the program.&lt;/p&gt;
&lt;p&gt;The console spits out a stack trace&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;Traceback&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;most&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;recent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;call&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;last&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;:
&lt;span class="nv"&gt;File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/home/andy/main.py"&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;22&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;module&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="ss"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;~~~~^^&lt;/span&gt;
&lt;span class="nv"&gt;File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/home/andy/main.py"&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;18&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nv"&gt;logger&lt;/span&gt;.&lt;span class="nv"&gt;info&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;divide&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;,&lt;span class="nv"&gt;b&lt;/span&gt;&lt;span class="ss"&gt;))&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="o"&gt;~~~~~~^^^^^&lt;/span&gt;
&lt;span class="nv"&gt;File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/home/andy/main.py"&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;divide&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nv"&gt;b&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="o"&gt;~^~&lt;/span&gt;
&lt;span class="nv"&gt;ZeroDivisionError&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;division&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;zero&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The &lt;code&gt;app.log&lt;/code&gt; file contains a single line:&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="mf"&gt;2025&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;07&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;14&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;22&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;20&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;04&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;551&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__main__&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="gotcha"&gt;Gotcha&lt;a class="headerlink" href="#gotcha" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Where is the gotcha here? The stack trace is right there!&lt;/p&gt;
&lt;p&gt;You are, of course, right. However, imagine that this was not a simple application, but instead a production application that sends logs to a central service. Your application crashed and now one was watching the console. Your &lt;code&gt;app.log&lt;/code&gt; file has no information. It says the application started and then...nothing. What happened? Is it still running?&lt;/p&gt;
&lt;p&gt;As you dig through running processes, or check a &lt;code&gt;/health&lt;/code&gt; end point for responses, you find out that it isn't running. That took a lot of time, and production isn't responding.&lt;/p&gt;
&lt;p&gt;You've lost all visibility to what happened in your application at the most critical moment. When it crashed and spit out a stack trace, you want as much detail as you can get.&lt;/p&gt;
&lt;h2 id="solution"&gt;Solution&lt;a class="headerlink" href="#solution" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The solution is &lt;a href="https://docs.python.org/3/library/sys.html#sys.excepthook"&gt;sys.excepthook&lt;/a&gt;. This is called when any exception is raised and uncaught, except for &lt;code&gt;SystemExit&lt;/code&gt;. It's pretty easy to utilize as well. A few small changes to the above code will allow us to log this completely unexpected &lt;code&gt;ZeroDivisionError&lt;/code&gt;. &lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;logging&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;sys&lt;/span&gt;

&lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="vm"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setLevel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FileHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"app.log"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;formatter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;logging&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Formatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;%(asctime)s&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;%(name)s&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;%(levelname)s&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;%(message)s&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;setFormatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;formatter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;addHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;handle_uncaught_exception&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;exc_traceback&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;critical&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;"uncaught exception, application will terminate."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;exc_info&lt;/span&gt;&lt;span class="o"&gt;=&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;exc_traceback&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;excepthook&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;handle_uncaught_exception&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;divide&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="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="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Application start"&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="mi"&gt;10&lt;/span&gt;
    &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;divide&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;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Application end"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"__main__"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&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;The important bit is the new &lt;code&gt;handle_uncaught_exception&lt;/code&gt; function and the &lt;code&gt;sys.excepthook&lt;/code&gt; line (with appropriate &lt;code&gt;import&lt;/code&gt; statement). &lt;/p&gt;
&lt;p&gt;Someone running this in the console will notice that there is not a stack trace dumped to the console now. Instead, our log contains important information:&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="mf"&gt;2025&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;07&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;14&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;22&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;30&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;44&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;061&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__main__&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;INFO&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Application&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;
&lt;span class="mf"&gt;2025&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;07&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;14&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;22&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;30&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;44&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mf"&gt;061&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;__main__&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;CRITICAL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;uncaught&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;application&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;will&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;terminate&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;Traceback&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;most&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;recent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"/home/andy/main.py"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;31&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;module&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="err"&gt;~~~~&lt;/span&gt;&lt;span class="o"&gt;^^&lt;/span&gt;
&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"/home/andy/main.py"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;27&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nb"&gt;log&lt;/span&gt;&lt;span class="n"&gt;ger&lt;/span&gt;&lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="n"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;divide&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="w"&gt;                &lt;/span&gt;&lt;span class="err"&gt;~~~~~~&lt;/span&gt;&lt;span class="o"&gt;^^^^^&lt;/span&gt;
&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"/home/andy/main.py"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;divide&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;return&lt;/span&gt;&lt;span class="w"&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="w"&gt;        &lt;/span&gt;&lt;span class="err"&gt;~&lt;/span&gt;&lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="err"&gt;~&lt;/span&gt;
&lt;span class="n"&gt;ZeroDivisionError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;division&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;zero&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is great! Now when troubleshooting this failing application and looking at the logs, we can easily see that an exception occurred. Additionally, with proper updates to the logging, more context can be provide such as the values of &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt;. While it's easy enough to figure out that &lt;code&gt;b&lt;/code&gt; is &lt;code&gt;0&lt;/code&gt; in this simple example, the context in a larger production application could save a ton of troubleshooting time.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Logging is vital to knowing what your application is doing. But, it's even more important to determining why it stopped working. If your logs aren't providing information when the application crashes, it's functionally useless. By implementing an &lt;code&gt;excepthook&lt;/code&gt;, you can catch and properly log uncaught exceptions.&lt;/p&gt;
&lt;p&gt;I know some of you are coming up with alternatives. Terrible ideas like wrapping the entire main block in a &lt;code&gt;try/except&lt;/code&gt;. There are legitimate reasons to throw an exception. In this case, a &lt;code&gt;ZeroDivisionError&lt;/code&gt; is a great exception to catch. But, you'd want to do it around as small of a code block as possible.&lt;/p&gt;
&lt;p&gt;This is a clean way to catch truly unexcepted exceptions, not something a developer could have anticipated and, perhaps, fixed with additional input validation.&lt;/p&gt;</content><category term="Technical Solutions"/><category term="technical"/></entry><entry><title>How to fix GitLab 18 error regarding git_data_dirs</title><link href="https://andrewwegner.com/gitlab_18_git_data_dirs_resolution.html" rel="alternate"/><published>2025-07-06T08:00:00-05:00</published><updated>2025-07-06T08:00:00-05:00</updated><author><name>Andy Wegner</name></author><id>tag:andrewwegner.com,2025-07-06:/gitlab_18_git_data_dirs_resolution.html</id><summary type="html">&lt;p&gt;GitLab 18 removes &lt;code&gt;git_data_dirs&lt;/code&gt; and if you have been using it and didn't notice the deprecation warnings, an update to GitLab 18 will fail. This is a simple fix.&lt;/p&gt;</summary><content type="html">
&lt;h2 id="introduction"&gt;Introduction&lt;a class="headerlink" href="#introduction" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Several years ago I &lt;a href="https://andrewwegner.com/installing-gitlab.html"&gt;set up GitLab in my home environment&lt;/a&gt;. I apprecate past me documenting the basic steps I took to do this, because I'll need them again at some point when I make upgrades to my home lab. I've documented somethings I've done (like &lt;a href="https://andrewwegner.com/setting-up-gitlab-runners.html"&gt;setting up GitLab runners&lt;/a&gt;, or dealing with &lt;a href="https://andrewwegner.com/disable-grafana-in-gitlab-16.html"&gt;Grafana being deprecated within GitLab&lt;/a&gt; or utilzing &lt;a href="https://andrewwegner.com/obsidian-gitlab-setup.html"&gt;GitLab to automatically backup my Obsidian notes&lt;/a&gt;) Unfortunately, I didn't document everything. One of those things that I didn't document was changing where my repositories are stored on disk by default.&lt;/p&gt;
&lt;p&gt;In GitLab 17.8 (January 2025), a new deprecation warning started appearing.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;git_data_dirs has been deprecated since 17.8 and will be removed in 18.0. See https://docs.gitlab.com/omnibus/settings/configuration.html#migrating-from-git_data_dirs for migration instructions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I'm a little annoyed that there were only 5 months of notice on this because major versions are released in May. In any case, if you attempt to update to version 18 or beyond and are utilizing &lt;code&gt;git_data_dirs&lt;/code&gt;, the upgrade will fail.&lt;/p&gt;
&lt;h2 id="solution"&gt;Solution&lt;a class="headerlink" href="#solution" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As usual, GitLab is good at providing &lt;a href="https://docs.gitlab.com/omnibus/settings/configuration/#migrating-from-git_data_dirs"&gt;documentation on resoluting and migrating&lt;/a&gt; through the deprecations. However, on my first attempt I encountered an error. I believe it's because I missed a note buried in the text - not the code blocks - the first time.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Note that the &lt;code&gt;/repositories&lt;/code&gt; suffix must be appended to the path because it was previously appended internally.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The solution is to open &lt;code&gt;/etc/gitlab/gitlab.rb&lt;/code&gt; (you probably should make a backup first). Find the &lt;code&gt;git_data_dirs&lt;/code&gt; line. For me this was around line 455. Then comment out this entire block.&lt;/p&gt;
&lt;p&gt;Then find (or add) the &lt;code&gt;gitaly['configuration']&lt;/code&gt; block. This was immediately after the &lt;code&gt;git_data_dirs&lt;/code&gt; section for me. Uncomment it, and add the appropriate path (from your &lt;code&gt;git_data_dirs&lt;/code&gt; block) and add &lt;code&gt;/repositories&lt;/code&gt; to the end of it.&lt;/p&gt;
&lt;p&gt;Once you are done, run&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;gitlab-ctl reconfigure
gitlab-ctl restart
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Give it a minute to fire up all the GitLab subcomponents, and then you should be able to update GitLab beyond version 18.&lt;/p&gt;
&lt;h3 id="new-code-section"&gt;New code section&lt;a class="headerlink" href="#new-code-section" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;My &lt;code&gt;gitlab.rb&lt;/code&gt; now has this:&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="gh"&gt;#&lt;/span&gt;git_data_dirs({
&lt;span class="gh"&gt;#&lt;/span&gt;  "default" =&amp;gt; {
&lt;span class="gh"&gt;#&lt;/span&gt;    "path" =&amp;gt; "/previous/path/to/repos"
&lt;span class="gh"&gt;#&lt;/span&gt;   }
&lt;span class="gh"&gt;#&lt;/span&gt;})

&lt;span class="gu"&gt;##&lt;/span&gt;# Gitaly settings
gitaly['configuration'] = {
storage: [
    {
    name: 'default',
    path: '/previous/path/to/repos/repositories',
    },
],
}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With this quick change, I can continue to hold the repositories at a location of my choosing. The biggest thing is to add &lt;code&gt;/repositories&lt;/code&gt; to the &lt;code&gt;path&lt;/code&gt; in the new Gitly configuration. With this change, I can continue to utilizing the current version of GitLab - a tool that I still find invaluable.&lt;/p&gt;</content><category term="Technical Solutions"/><category term="technical"/><category term="gitlab"/></entry><entry><title>Python Gotcha: Identity vs Equality - When 'is' Fails Unexpectedly</title><link href="https://andrewwegner.com/python-gotcha-identity-vs-equality.html" rel="alternate"/><published>2025-06-17T08:00:00-05:00</published><updated>2025-06-17T08:00:00-05:00</updated><author><name>Andy Wegner</name></author><id>tag:andrewwegner.com,2025-06-17:/python-gotcha-identity-vs-equality.html</id><summary type="html">&lt;p&gt;To a new developer &lt;code&gt;is&lt;/code&gt; can look like an equality check in Python, especially in poorly written tutorials. I'll an overview of what &lt;code&gt;is&lt;/code&gt; is and how you should use it in only limited circumstances.&lt;/p&gt;</summary><content type="html">
&lt;h2 id="introduction"&gt;Introduction&lt;a class="headerlink" href="#introduction" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In the &lt;a href="https://andrewwegner.com/python-gotcha-comparisons.html"&gt;comparisons gotcha&lt;/a&gt; I wrote a few years ago, I briefly touched on &lt;a href="https://andrewwegner.com/python-gotcha-comparisons.html#is-vs"&gt;&lt;code&gt;is&lt;/code&gt; vs &lt;code&gt;==&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Put simply, &lt;code&gt;is&lt;/code&gt; should ONLY be used if you are checking if two references refer to the same object.
&lt;em&gt;Remember, &lt;code&gt;is&lt;/code&gt; compares object references.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Even more simply, &lt;code&gt;is&lt;/code&gt; is &lt;em&gt;not&lt;/em&gt; checking value. Let's take a look at a couple examples.&lt;/p&gt;
&lt;h2 id="gotcha"&gt;Gotcha&lt;a class="headerlink" href="#gotcha" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="integers"&gt;Integers&lt;a class="headerlink" href="#integers" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Python, specifically CPython, caches the values of &lt;code&gt;-5&lt;/code&gt; through &lt;code&gt;256&lt;/code&gt; (inclusive). This means that these small integer values will always refer to the same object. &lt;/p&gt;
&lt;p&gt;Note the phrasing there - "the same object".&lt;/p&gt;
&lt;p&gt;Outside of that range, though, the same is not true. &lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; a = 100
&amp;gt;&amp;gt;&amp;gt; b = 100
&amp;gt;&amp;gt;&amp;gt; a is b
True
&amp;gt;&amp;gt;&amp;gt; a = 257
&amp;gt;&amp;gt;&amp;gt; b = 257
&amp;gt;&amp;gt;&amp;gt; a is b
False
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In both of the above examples, using &lt;code&gt;a == b&lt;/code&gt; would have returned &lt;code&gt;True&lt;/code&gt;. The mistake was assuming that &lt;code&gt;is&lt;/code&gt; does the same thing. It does not.&lt;/p&gt;
&lt;h3 id="strings"&gt;Strings&lt;a class="headerlink" href="#strings" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;String interning is a method of storing only one copy of each distinct immutable string value. Immutable strings can't be changed. Not every string will be interned though. Let's take a look:&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; a = "Hello"
&amp;gt;&amp;gt;&amp;gt; b = "Hello"
&amp;gt;&amp;gt;&amp;gt; a is b
True
&amp;gt;&amp;gt;&amp;gt; a = "Hello World"
&amp;gt;&amp;gt;&amp;gt; b = "Hello World"
&amp;gt;&amp;gt;&amp;gt; a is b
False
&amp;gt;&amp;gt;&amp;gt; a = "Hello_World"
&amp;gt;&amp;gt;&amp;gt; b = "Hello_World"
&amp;gt;&amp;gt;&amp;gt; a is b
True
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The first and last example interned the strings, showing that &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; refer to the same object. But, the second example - &lt;code&gt;Hello World&lt;/code&gt; - didn't get interned, so &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; refer to different objects. Why is this?&lt;/p&gt;
&lt;p&gt;The short and simply answer is that any string that has only numbers, letters or underscores will be interned. Since &lt;code&gt;Hello World&lt;/code&gt; contains a &lt;code&gt;space&lt;/code&gt;, it would not be interned.&lt;/p&gt;
&lt;h2 id="the-solution"&gt;The Solution&lt;a class="headerlink" href="#the-solution" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To a new developer that has seen tutorials that read &lt;code&gt;if a is True&lt;/code&gt; or &lt;code&gt;if b is None&lt;/code&gt;, a conditional for integers or strings following the same pattern &lt;em&gt;appears&lt;/em&gt; to be comparing values. If they test it with small, positive numbers or simple one word strings, the assumption holds up. &lt;/p&gt;
&lt;p&gt;But, &lt;code&gt;==&lt;/code&gt; is for comparing values! Each of the above examples would return &lt;code&gt;True&lt;/code&gt; by changing the statement to &lt;code&gt;a == b&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The few times that &lt;code&gt;is&lt;/code&gt; is appropriate are when you are checking &lt;code&gt;True&lt;/code&gt;/&lt;code&gt;False&lt;/code&gt; or &lt;code&gt;None&lt;/code&gt;. Otherwise, the &lt;em&gt;vast&lt;/em&gt; majority of the time, you want to use an equality check (&lt;code&gt;==&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;The Python &lt;a href="https://peps.python.org/pep-0008/#programming-recommendations"&gt;PEP8 programming recommendations&lt;/a&gt; state:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Comparisons to singletons like &lt;code&gt;None&lt;/code&gt; should always be done with &lt;code&gt;is&lt;/code&gt; or &lt;code&gt;is not&lt;/code&gt;, never the equality operators.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The linters in the Python ecosystem report on the usage of &lt;code&gt;is&lt;/code&gt; vs &lt;code&gt;==&lt;/code&gt; too. &lt;code&gt;flake8&lt;/code&gt; has &lt;a href="https://www.flake8rules.com/rules/E711.html"&gt;E711&lt;/a&gt; - &lt;code&gt;Comparison to None should be 'cond is None:'&lt;/code&gt;. &lt;code&gt;ruff&lt;/code&gt; has a similar report with it's &lt;a href="https://docs.astral.sh/ruff/rules/none-comparison/"&gt;&lt;code&gt;None&lt;/code&gt; comparison&lt;/a&gt; check.&lt;/p&gt;
&lt;p&gt;I highly recommend a linter for your projects to catch this, and other problems that go against best practices. &lt;/p&gt;
&lt;p&gt;&lt;em&gt;Remember, &lt;code&gt;is&lt;/code&gt; compares object references, not object equality&lt;/em&gt;&lt;/p&gt;</content><category term="Technical Solutions"/><category term="technical"/></entry><entry><title>Moving MS Authenticator to a new phone without a personal account</title><link href="https://andrewwegner.com/ms-authenticator-without-personal-account.html" rel="alternate"/><published>2024-11-27T12:15:00-06:00</published><updated>2024-11-27T12:15:00-06:00</updated><author><name>Andy Wegner</name></author><id>tag:andrewwegner.com,2024-11-27:/ms-authenticator-without-personal-account.html</id><summary type="html">&lt;p&gt;The Microsoft Authenticator application allows you to transfer tokens to a new device if you have a personal Microsoft account. How do you do it if you don't have a personal account? This is a quick walk through of what worked for me.&lt;/p&gt;</summary><content type="html">
&lt;h2 id="introduction"&gt;Introduction&lt;a class="headerlink" href="#introduction" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Work utilizes the Microsoft Authenticator app as their 2FA application of choice. It's not my favorite, but it does what it needs to do, and since I only use the application for work things, it's nice to keep work accounts out of personal accounts in my other 2FA application. Recently, I got a new phone, and needed to migrate everything over to that device. As an aside, Android continues to make this easier and easier. &lt;/p&gt;
&lt;p&gt;Standard advice on how to migrate your token to a new device is to enable cloud backup, sign in to the new device and import the backup. The problem is when you go to enable Cloud Backup from the settings, you are hit with this message:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"You need a personal Microsoft account to use cloud backup"&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Well, I don't have a personal Microsoft account. &lt;/p&gt;
&lt;p&gt;&lt;em&gt;Aside: I can't provide screenshots of the Android application for this post because the application prevents screenshots from being taken. I appreciate this feature as part of the application for security purposes, but it's annoying when trying to write about it. Oh well...&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="alternative-to-cloud-backup"&gt;Alternative to Cloud Backup&lt;a class="headerlink" href="#alternative-to-cloud-backup" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I have set up MFA on my work account - obviously - and can access settings through the &lt;a href="https://aka.ms/mfasetup"&gt;Microsoft MFA setup page&lt;/a&gt;. From here, you'll be on the Security Info page. It will show the current authenticator device being utilized. You'll need the old device during this process to authenticate one last time.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Microsoft security information page showing my current MS Authenticator device" src="https://andrewwegner.com/images/ms_authenticator/security_info.png"/&gt;&lt;/p&gt;
&lt;p&gt;Click on "Add sign-in method" and then "Microsoft Authenticator"&lt;/p&gt;
&lt;p&gt;&lt;img alt="Options to pick from for adding a new signin method. For this, select Microsoft Authenticator" src="https://andrewwegner.com/images/ms_authenticator/new_method.png"/&gt;&lt;/p&gt;
&lt;p&gt;On your phone, ensure you have the MS Authenticator application installed. Then follow the on screen prompts between your computer and your new phone. You'll select "Work or School" and then scan the QR code that is common with 2FA/MFA applications.&lt;/p&gt;
&lt;p&gt;The last step in the process will be to send an authentication request to the new device. Do so, enter the verification code, and your new device will be added.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Device is approved after entering your verification code" src="https://andrewwegner.com/images/ms_authenticator/approved_device.png"/&gt;&lt;/p&gt;
&lt;p&gt;I recommend you wrap this up by removing your old device from the Security Info section now that the new device is approved. &lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Microsoft authenticator application doesn't make moving to a new device as easy as some of its competitors. The requirement for a personal Microsoft account to go through the most commonly recommended way is also a non-starter if you don't have such an account already. Fortunately, the alternative posted above ended up working just fine. I assume I'll need a new phone again, and this should help at least me, remember that there is an easy enough way to migrate.&lt;/p&gt;</content><category term="Technical Solutions"/><category term="technical"/></entry><entry><title>Format USB drive with EFI partition in Windows</title><link href="https://andrewwegner.com/format-efi-usb-windows.html" rel="alternate"/><published>2024-11-13T13:00:00-06:00</published><updated>2024-11-13T13:00:00-06:00</updated><author><name>Andy Wegner</name></author><id>tag:andrewwegner.com,2024-11-13:/format-efi-usb-windows.html</id><summary type="html">&lt;p&gt;Windows protects EFI partitions from being deleted. This article is a quick walkthrough on how to format a USB drive with an EFI partition within Windows.&lt;/p&gt;</summary><content type="html">
&lt;h2 id="introduction"&gt;Introduction&lt;a class="headerlink" href="#introduction" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I seem to collect USB drives. They are handed out at conferences. They are used as recovery media for new machines. They just randomly appear on my desk. I use them to build live Linux CDs, transferring media from one device to another, building recovery media, and generally the things you'd expect a USB drive to be used for. Generally, when I use a USB drive the first thing I do is format it. My USB drives are not for long term storage and I consider anything on them used one time then the drive goes back to the bin for reuse another time. &lt;/p&gt;
&lt;p&gt;An annoyance with this, though, is that when I use a drive to build a bootable backup media, often times an EFI partition will be created. As soon as this is created, Windows prevents that partition from being deleted in the future without taking additional steps. I have to go look those up every time because I do it infrequently enough to remember. This is a simple walkthrough of my process to solve this problem&lt;/p&gt;
&lt;p&gt;A USB drive with an EFI partition looks like this within the Disk Management tool in Windows&lt;/p&gt;
&lt;p&gt;&lt;img alt="A USB drive with an EFI partition and an unallocated partition" src="https://andrewwegner.com/images/efi-partition.png"/&gt;&lt;/p&gt;
&lt;h2 id="format-an-efi-partition"&gt;Format an EFI Partition&lt;a class="headerlink" href="#format-an-efi-partition" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To remove this partition, utilize the built in &lt;code&gt;diskpart&lt;/code&gt; tool. &lt;code&gt;Win&lt;/code&gt; + &lt;code&gt;R&lt;/code&gt; and type in &lt;code&gt;diskpart&lt;/code&gt; and allow the application to run. &lt;/p&gt;
&lt;p&gt;First you need to find which drive you want to adjust partitions on. &lt;/p&gt;
&lt;div class="admonition attention"&gt;
&lt;p class="admonition-title"&gt;Attention&lt;/p&gt;
&lt;p&gt;Ensure you select the correct disk. Failure to do so could result in an inoperable system&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Plug the USB drive into the machine and run &lt;code&gt;list disk&lt;/code&gt; to see all available disks.&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;DISKPART&amp;gt; list disk

Disk ###  Status         Size     Free     Dyn  Gpt
--------  -------------  -------  -------  ---  ---
Disk 0    Online         3726 GB  1024 KB        &lt;span class="gs"&gt;*&lt;/span&gt;
&lt;span class="gs"&gt;Disk 1    Online          931 GB  1024 KB        *&lt;/span&gt;
Disk 2    Online           14 GB    14 GB
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You can see that I have 3 drives detected. &lt;code&gt;Disk 2&lt;/code&gt; is my USB drive.&lt;/p&gt;
&lt;p&gt;Select the disk.&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;DISKPART&amp;gt; sel disk 2

Disk 2 is now the selected disk.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Then show the partitions on the disk.&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;DISKPART&amp;gt; list partition

Partition ###  Type              Size     Offset
-------------  ----------------  -------  -------
Partition 1    System            4000 KB   850 KB
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;There is a single partition - the EFI partition. The rest of the drive is unallocated, as the screenshot above shows. Select this partition. IF you have multiple partitions listed, you'll need to determine which is the EFI partition. In my case, since I'm going to be formatting the entire drive anyway, if there were multiple listed I'd select and delete each individual partition. &lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;DISKPART&amp;gt; sel partition 1

Partition 1 is now the selected partition.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;div class="admonition attention"&gt;
&lt;p class="admonition-title"&gt;Attention&lt;/p&gt;
&lt;p&gt;These commands will delete a partition. If you haven't selected the right drive or partition things will break.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Finally, delete the EFI partition&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;DISKPART&amp;gt; delete partition override
DiskPart successfully deleted the selected partition.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;From the disk management tool, you can verify it's gone and format as normal.&lt;/p&gt;
&lt;p&gt;&lt;img alt="All EFI partitions removed" src="https://andrewwegner.com/images/efi-partition-removed.png"/&gt;&lt;/p&gt;</content><category term="Technical Solutions"/><category term="technical"/></entry><entry><title>Controlling power to LED lights with WLED Controller and a Relay switch</title><link href="https://andrewwegner.com/control-power-wled-relay.html" rel="alternate"/><published>2024-08-16T12:00:00-05:00</published><updated>2024-08-16T12:00:00-05:00</updated><author><name>Andy Wegner</name></author><id>tag:andrewwegner.com,2024-08-16:/control-power-wled-relay.html</id><summary type="html">&lt;p&gt;Wiring a lot of LEDs requires more power than the small WLED controller can handle, but leaving a large power supply running even when the lights are off is inefficient. This post talks about the progress on the project and how I wired in a relay to keep everything running efficiently.&lt;/p&gt;</summary><content type="html">
&lt;h2 id="introduction"&gt;Introduction&lt;a class="headerlink" href="#introduction" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;It's time to continue my summer project of setting up some outdoor LED lights. &lt;a href="https://andrewwegner.com/update-wled-ericsity-controller-0141.html"&gt;Previously&lt;/a&gt;, I &lt;a href="https://www.amazon.com/Ericsity-Controller-Addressable-WS2812B-SK6812/dp/B0CNVXY8NX"&gt;set up and updated the Ericsity controller&lt;/a&gt; with &lt;a href="https://kno.wled.ge/"&gt;WLED&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I am putting up approximiately 20 meters - 65 feet - of LEDs. Total this will be nearly 2,000 individual LEDs and approximately 665 individually controlled LED segments (3 LEDs per segment). This will take more power than the little controller can handle. To demonstrate the problem of powering all of these LEDs with only the controller, look at this image:&lt;/p&gt;
&lt;p&gt;&lt;img alt="Power drop across 20 meters of LEDs" src="https://andrewwegner.com/images/wled/voltage-drop.png"/&gt;&lt;/p&gt;
&lt;p&gt;These strips are wired with the end of one strip connected to the start of the next. The top strip is connected directly to the controller. The one below is the end of strip two, followed by the start of strip three and the bottom is the very end of the full run of LEDs. The lights are all set to the same color, but as you can see they clearly aren't the same color. The voltage drop across 65 feet of LEDs means that the LEDs are the end can't get enough power to match their earlier siblings.&lt;/p&gt;
&lt;h2 id="power-injection"&gt;Power Injection&lt;a class="headerlink" href="#power-injection" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The solution to this specific problem is to inject power into the LED strips. I picked up some &lt;a href="https://www.amazon.com/dp/B0957T1S9C"&gt;WAGO connectors&lt;/a&gt; and a &lt;a href="https://www.amazon.com/dp/B0BXTP524R"&gt;NUOFUWEI power supply&lt;/a&gt;. I also had some 18 guage wire on hand. With this power unit, I can easily set up three injection points.&lt;/p&gt;
&lt;p&gt;The goal was to inject power at the start of strip 1, right where the controller is connecting in the image above. Then inject between strips 2 and 3, in the middle of the run. Finally, I injected power at the end of strip 4. With these three, equally spaced injection points, I was able to get a nice uniform color across the entire 20 meter run.&lt;/p&gt;
&lt;p&gt;&lt;img alt="LED strip with power injection shows uniform coloring" src="https://andrewwegner.com/images/wled/equal-voltage.png"/&gt;&lt;/p&gt;
&lt;p&gt;Problem Solved! Right?&lt;/p&gt;
&lt;p&gt;Not exactly.&lt;/p&gt;
&lt;h2 id="turning-off-the-psu"&gt;Turning off the PSU&lt;a class="headerlink" href="#turning-off-the-psu" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;WLED controls the lights, but doesn't control the power supply with this wiring setup. Turning off the lights within WLED does turn off the LEDs, but the power supply continues to run. It's not drawing at full load, but it is drawing power and the cooling fan is active. It's noticable and unneeded. I want a way to turn off the power supply AND the LEDs at the same time. &lt;/p&gt;
&lt;p&gt;I can't power the WLED controller from the large power supply to do this, because if I turn off the power supply that'd also turn off the WLED controller. I'll need to power the WLED controller independently from the lights. Fortunately, this won't be a problem. &lt;/p&gt;
&lt;p&gt;The next step is figuring out how I can use WLED to control the larger power supply. Fortunately, &lt;a href="https://kno.wled.ge/features/relay-control/"&gt;WLED has the ability to control a relay&lt;/a&gt;, which I can use to control the power supply. The Ericsity controller also has two output data pins. While I don't think the second one was built in to control a relay, it works perfectly here. &lt;/p&gt;
&lt;h3 id="wiring"&gt;Wiring&lt;a class="headerlink" href="#wiring" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;I purchased a &lt;a href="https://capitaloneshopping.com/p/hi-letgo-5-v-1-channel-relay-mod/2RDBGLR8VL"&gt;HiLetGo relay&lt;/a&gt; so that I could toggle the larger power supply on and off. To do this, it's important that the data line and the ground are common among the controller, the power supply and the LEDs.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Wiring diagram for WLED controller, external power supply, LEDs and a relay controlling the external PSU" src="https://andrewwegner.com/images/wled/deck-lights-wled.png"/&gt;&lt;/p&gt;
&lt;p&gt;The diagram above is a rough schematic of how I wired this. The controller sends data, but not power, to the LEDs. Data was on GPIO 16. The important part here is that the data line is shared across all of the strips. I did not need a signal booster for my project, and because I'm about to use the second exposed data channel for the relay, I had to ensure that this single channel could send a signal down the entire length of the strip. Fortunately, I didn't have any issues.&lt;/p&gt;
&lt;p&gt;The wiring ground was tied into the PSU and the LEDs as well. The common ground is important.&lt;/p&gt;
&lt;p&gt;On the other side of the diagram is the relay. I put this relay between the wall and the PSU. When WLED sent an &lt;code&gt;ON&lt;/code&gt; signal, it would close the relay, turning on the PSU and the LEDs. GPIO 2 was tied to the relay and within &lt;code&gt;LED Preferences&lt;/code&gt;, the Relay Pin was set to GPIO 2. &lt;/p&gt;
&lt;p&gt;One quick power cycle from within WLED is required at this point. Press the power button in the UI to turn everything off, press it once more to turn it on. As long as the PSU is plugged into the wall, you should here it fire up and see the LEDs turn on. If you press the power button again, the LEDs turn off, the relay clicks, shuts down the PSU and only the WLED controller remains active.&lt;/p&gt;
&lt;h2 id="success"&gt;Success&lt;a class="headerlink" href="#success" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With the relay in place, the LEDs don't draw phantom power while off because the PSU isn't active. The added benefit, at least for this specific power unit, is the the fans aren't running constantly so it's not as loud. While this will eventually be outside, I'd still prefer to not hear the fan when the lights are off. While they are active it's not going to be bothering me, because I'll likely have music playing for the sound reactive features which will easily be louder than this fan.&lt;/p&gt;
&lt;p&gt;The next step in the project is going to be to get this set up outside. &lt;/p&gt;</content><category term="Technical Solutions"/><category term="technical"/><category term="wled"/></entry><entry><title>Setting up and updating the Ericsity WLED Controller from 0.13.3 to 0.14.1</title><link href="https://andrewwegner.com/update-wled-ericsity-controller-0141.html" rel="alternate"/><published>2024-06-20T15:45:00-05:00</published><updated>2024-06-20T15:45:00-05:00</updated><author><name>Andy Wegner</name></author><id>tag:andrewwegner.com,2024-06-20:/update-wled-ericsity-controller-0141.html</id><summary type="html">&lt;p&gt;The Ericsity WLED controller comes with WLED 0.13.3 preinstalled and only offers the ability to update to 0.13.4. This walks through setting up the controller for the first time and moving to 0.14.1 while maintaining the sound reactive features the controller advertises.&lt;/p&gt;</summary><content type="html">
&lt;h2 id="introduction"&gt;Introduction&lt;a class="headerlink" href="#introduction" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;My summer time project this year is to set up some LED lights outside. After testing out a couple Govee strips that I didn't like because I couldn't diffuse the individual lights away, I finally found an LED strip I liked. The next step was to control these strips so that I could do more than solid colors or the default rainbow every LED strip has. I settled on using the amazing &lt;a href="https://kno.wled.ge/"&gt;WLED project&lt;/a&gt; to control the lights. To speed up the project, I decided to get a prebuilt controller and eventually selected the &lt;a href="https://www.amazon.com/Ericsity-Controller-Addressable-WS2812B-SK6812/dp/B0CNVXY8NX"&gt;Ericsity controller with a built in mic&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Side note: This post is just about how I updated the controller. The next article in the post is available, and talks about how &lt;a href="https://andrewwegner.com/control-power-wled-relay.html"&gt;I am controlling a larger power supply with the WLED controller&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;a class="headerlink" href="#the-problem" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The controller arrived and was easy to set up. I wired up my strips in the basement to ensure I didn't have any power problems and then determined where I needed to make cuts and solder points once this was outside. Then I went to set up &lt;a href="https://kno.wled.ge/features/segments/"&gt;segments&lt;/a&gt; in WLED so that I could control effects in specific areas.&lt;/p&gt;
&lt;p&gt;The problem I quickly encountered was that I am outlining my deck with these lights and right in the middle of the deck is a set of steps that I also wanted to light up and then the rest of the deck rail. WLED doesn't have the ability (at least that I've found) to span an effect across multiple segments and I'd need multiple segments for this. &lt;/p&gt;
&lt;p&gt;At minimium I'd need 3 segments - The rail, the steps, the rest of the rail. If I want an effect to span the entire rail, I can't do that with the steps in the middle. Further research pointed me to the ability to &lt;a href="https://kno.wled.ge/advanced/mapping/"&gt;remap the physical LED order&lt;/a&gt; to a logical LED order. After trying to set this up, it wasn't feature complete in 0.13, but did appear to be in 0.14.&lt;/p&gt;
&lt;p&gt;However, the Ericsity - rightfully so - utilizes a &lt;a href="https://github.com/atuline/WLED"&gt;stable version of WLED&lt;/a&gt; that only allows you to &lt;a href="https://github.com/atuline/WLED/releases"&gt;update to 0.13.4&lt;/a&gt; and the README has the following note:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This repository is still maintained, and will receive bugfixes. However no new features will be added.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img alt="WLED Sound Reactive update page showing that upgrades are only available through version 0.13.4" src="https://andrewwegner.com/images/wled/wled-update-page.png"/&gt;&lt;/p&gt;
&lt;h2 id="setting-up-ericsity-controller"&gt;Setting up Ericsity Controller&lt;a class="headerlink" href="#setting-up-ericsity-controller" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Let's take a step back and get the controller set up and then focus on upgrading it. Fortunately, Ericsity makes this pretty easy.&lt;/p&gt;
&lt;p&gt;Step 1 is to plug in the controller. After a few seconds, you will see a new network available on your phone called &lt;code&gt;WLED-AP&lt;/code&gt;. Join that network. From your browser navigate to &lt;code&gt;4.3.2.1&lt;/code&gt; and select "WIFI SETTINGS". &lt;/p&gt;
&lt;p&gt;&lt;img alt="WLED install page" src="https://andrewwegner.com/images/wled/install1.png"/&gt;&lt;/p&gt;
&lt;p&gt;The goal is to add this controller to your wireless network. Do this by adding your network name and password into the first two text boxes. I also updated the mDNS setting, but that's optional. Then save and connect.&lt;/p&gt;
&lt;p&gt;&lt;img alt="WLED network set up page" src="https://andrewwegner.com/images/wled/install2.png"/&gt;&lt;/p&gt;
&lt;p&gt;Finally, install the WLED mobile application if you are going to manage this via your phone. On an android device you can find it in the Google Play store with the name &lt;a href="https://play.google.com/store/apps/details?id=com.aircoookie.WLED&amp;amp;hl=en_US"&gt;WLED&lt;/a&gt;. Reconnect to your wireless network and the one you just added the controller to. Once installed and reconnected to the network, open the application. &lt;/p&gt;
&lt;p&gt;If you have already installed and configured a controller, you'll see your previous controllers listed. To add the new controller, click on the &lt;code&gt;+&lt;/code&gt; sign in the upper right. Then "Start Discovery". When the controller is found, you can press the check button.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Searching for and adding a new controller to the WLED application" src="https://andrewwegner.com/images/wled/install3.png"/&gt;&lt;/p&gt;
&lt;p&gt;The new controller will be listed on the main WLED page with the default name of &lt;code&gt;WLED-SoundReactive&lt;/code&gt;. Select that controller by tapping it. Then select "Config" along the top and "Security &amp;amp; Updates" at the bottom.&lt;/p&gt;
&lt;p&gt;Scroll all the way down to the bottom of this page and you should see that you have the following version installed:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;WLED SR version 0.13.3&lt;/code&gt;&lt;/p&gt;
&lt;h2 id="update-to-014x"&gt;Update to 0.14.x&lt;a class="headerlink" href="#update-to-014x" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;From the Security &amp;amp; Updates page, it makes sense to try and perform and update. If you click "Manual OTA Update" you'll notice that only version &lt;code&gt;0.13.3&lt;/code&gt; is available via this link though. It's time to do a manual update!&lt;/p&gt;
&lt;p&gt;I am utilizing the &lt;a href="https://github.com/MoonModules/WLED"&gt;MoonModules branch&lt;/a&gt; because the README for the default install says that changes should be made against this branch.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Pull Requests should be created against the MoonModules mdev branch.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I performed this OTA upgrade from my computer by navigating to the mDNS I set up previously. It can also be accessed by the IP address of the controller in your browser.&lt;/p&gt;
&lt;p&gt;At the time of this post, the &lt;a href="https://github.com/MoonModules/WLED/releases"&gt;current release&lt;/a&gt; was &lt;code&gt;0.14.1-b30&lt;/code&gt;. This was released approximately 6 months ago. I briefly skimmed through recent issues and pull requests to see if I should find a more recent build. There was an &lt;a href="https://github.com/MoonModules/WLED/issues/130"&gt;crash issue&lt;/a&gt; reported with the &lt;code&gt;ripple&lt;/code&gt; effects. Since I like that particular effect, I decided to go with a newer build than the official release. &lt;/p&gt;
&lt;h3 id="official-build"&gt;Official build&lt;a class="headerlink" href="#official-build" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you are going to stick with an official release, navigate to the &lt;a href="https://github.com/MoonModules/WLED/releases"&gt;release page&lt;/a&gt; and download the binary file you want to install. For the Ericsity, I found that the generic ESP32 build worked. If you want to use this, look for the file titled &lt;code&gt;WLEDMM_0.14.1-b30.36_esp32_4MB_M.bin&lt;/code&gt;. You could also use the &lt;code&gt;WLEDMM_0.14.1-b30.36_esp32_4MB_S.bin&lt;/code&gt;. Download this file.&lt;/p&gt;
&lt;h3 id="selecting-a-more-recent-build"&gt;Selecting a more recent build&lt;a class="headerlink" href="#selecting-a-more-recent-build" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Since I wanted a fix for the item I &lt;a href="https://github.com/MoonModules/WLED/issues/130"&gt;found&lt;/a&gt;, I opted to download a recent build of the &lt;code&gt;mdev&lt;/code&gt; branch. This can be accomplished by navigating to the build pipeline and filtering for the &lt;code&gt;mdev&lt;/code&gt; branch. This is on Github and available via this direct link to the &lt;a href="https://github.com/MoonModules/WLED/actions/workflows/wled-ci.yml?query=branch%3Amdev"&gt;mdev pipeline&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Recent builds on Github. Look for mdev branch" src="https://andrewwegner.com/images/wled/github-builds.png"/&gt;&lt;/p&gt;
&lt;p&gt;From here, select the build you want and scroll all the way to the bottom of the page. Find the binary you want to download. In my case, I wanted the &lt;code&gt;firmware-esp32_4MB_M&lt;/code&gt; and download it. Once downloaded, extract it so that you can upload the &lt;code&gt;.bin&lt;/code&gt; file within the WLED page. &lt;/p&gt;
&lt;p&gt;&lt;img alt="Select the build to utilize. For the Ericsity controller, I found the firmware-esp32_4MB_M version works" src="https://andrewwegner.com/images/wled/github-build-version.png"/&gt;&lt;/p&gt;
&lt;h3 id="press-the-button"&gt;Press the button!&lt;a class="headerlink" href="#press-the-button" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;In WLED's Security and Update page, select this binary and then click "Update!"&lt;/p&gt;
&lt;p&gt;&lt;img alt="Upload the binary to utilize as the new image" src="https://andrewwegner.com/images/wled/update1.png"/&gt;&lt;/p&gt;
&lt;p&gt;The update took less than a minute for me. While the new image was being installed, I was told not to close or refresh the page.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Update installing, don't close or refresh" src="https://andrewwegner.com/images/wled/update2.png"/&gt;&lt;/p&gt;
&lt;p&gt;When the update is complete, the controller will reboot. &lt;/p&gt;
&lt;p&gt;&lt;img alt="Update complete, rebooting" src="https://andrewwegner.com/images/wled/update3.png"/&gt;&lt;/p&gt;
&lt;p&gt;To confirm that everything has been updated, click on "Config". Immediately, you'll notice a lot more options. Scroll to the bottom and select "Security &amp;amp; Updates" and at the bottom you'll have an About section that lists the version you selected to install. Mine looks like this:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;WLEDMM version 0.14.1-b31.38 ☾&lt;/code&gt;&lt;/p&gt;
&lt;h3 id="fixing-sound-reactive"&gt;Fixing Sound Reactive&lt;a class="headerlink" href="#fixing-sound-reactive" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;During testing, I noticed that the sound reaction wasn't working. I found the AudioReactive plugin was disabled by default. This is an easy fix. In the WLED application (or web page), click on "Info" then click the power button icon next to "AudioReactive". However, this isn't enough to solve the problem. Click on "Config" then scroll down to AudioReactive. First make sure it is enabled (it should be after the power button selection above). &lt;/p&gt;
&lt;p&gt;The digitalmic section needs to be modified. The Ericsity controller has the microphone on the following pins:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Microphone Type: Generic I2S&lt;/li&gt;
&lt;li&gt;Pin I2S SD: 26&lt;/li&gt;
&lt;li&gt;Pin I2S WS: 5&lt;/li&gt;
&lt;li&gt;Pin I2S SCK: 21&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="Correct audio settings for Ericsity controller. Pin 26 for SD, Pin 5 for WS and Pin 21 for SCK" src="https://andrewwegner.com/images/wled/correct-audio.png"/&gt; &lt;/p&gt;
&lt;p&gt;I found that pin 5 was selected for another plugin. If you find this too, navigate back to Config page and select "Rotary-Encoder". I don't utilize this plugin, but it is part of the &lt;code&gt;M&lt;/code&gt; build I downloaded. Disable the plugin and change the &lt;code&gt;CLK Pin&lt;/code&gt; to be &lt;code&gt;undefined&lt;/code&gt;. Then save this change and go back to the AudioReactive plugin and set it up with the pin layout above.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Disable the Rotary-Encoder plugin and set the CLK pin to undefined" src="https://andrewwegner.com/images/wled/disabled-plugin.png"/&gt;&lt;/p&gt;
&lt;p&gt;Finally, reboot the controller by selecting "Info" and rebooting the controller at the bottom.&lt;/p&gt;
&lt;h2 id="good-to-go"&gt;Good to go&lt;a class="headerlink" href="#good-to-go" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Now that the controller has rebooted, audio reactions are working again. The branch I've updated by controller to is actively maintained for the 0.14 branch, even if it is several months behind the main line. I'm satisfied with this for now and will begin experimenting with the led mappings that I need that started this entire upgrade process.&lt;/p&gt;</content><category term="Technical Solutions"/><category term="technical"/><category term="wled"/></entry><entry><title>Python Gotcha: strip, lstrip, rstrip can remove more than expected</title><link href="https://andrewwegner.com/python-gotcha-strip-functions-unexpected-behavior.html" rel="alternate"/><published>2024-03-29T09:00:00-05:00</published><updated>2024-03-29T09:00:00-05:00</updated><author><name>Andy Wegner</name></author><id>tag:andrewwegner.com,2024-03-29:/python-gotcha-strip-functions-unexpected-behavior.html</id><summary type="html">&lt;p&gt;The Python strip, lstrip, and rstrip functions can have unexpected behavior. Even though this is documented, non-default values passed to these functions can lead to unexpected results and how Python 3.9 solved this with two new functions.&lt;/p&gt;</summary><content type="html">
&lt;h2 id="introduction"&gt;Introduction&lt;a class="headerlink" href="#introduction" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As a software engineer, you've cleaned your fair share of dirty strings. Removing leading or trailing spaces is probably one of the most common things done to user input. &lt;/p&gt;
&lt;p&gt;In Python, this is done with the &lt;a href="https://docs.python.org/3.10/library/stdtypes.html#str.strip"&gt;&lt;code&gt;.strip()&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://docs.python.org/3.10/library/stdtypes.html#str.lstrip"&gt;&lt;code&gt;.lstrip()&lt;/code&gt;&lt;/a&gt; or &lt;a href="https://docs.python.org/3.10/library/stdtypes.html#str.rstrip"&gt;&lt;code&gt;.rstrip()&lt;/code&gt;&lt;/a&gt; functions and generally looks like this:&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; "     Andrew Wegner     ".lower().strip()
'andrew wegner'
&amp;gt;&amp;gt;&amp;gt; "     Andrew Wegner     ".lower().lstrip()
'andrew wegner     '
&amp;gt;&amp;gt;&amp;gt; "     Andrew Wegner     ".lower().rstrip()
'     andrew wegner'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That's pretty straightforward and nothing unexpected in going on. &lt;/p&gt;
&lt;h2 id="gotcha"&gt;Gotcha&lt;a class="headerlink" href="#gotcha" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The Gotcha is that each of these functions take a list of characters that can be removed. &lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; "Andrew Wegner".lower().rstrip(" wegner")
'and'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;What happened? Why wasn't the result just&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;'andrew'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="explanation"&gt;Explanation&lt;a class="headerlink" href="#explanation" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Read line from the documentation again, carefully:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A list of &lt;strong&gt;characters&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Not a list of strings.&lt;/p&gt;
&lt;p&gt;This is explicitly spelled out in the documentation, with an example, showing what the implications are. However, for a new developer, it's unexpected behavior. After all, these seem like intutive functions. &lt;/p&gt;
&lt;p&gt;The example with my does the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Receives a list of characters to remove. In this case it is all letters in my last name, plus the space character: &lt;code&gt;wegner&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Lower case all letters in the input string, resulting in &lt;code&gt;andrew wegner&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;From the right hand side of the string, begin removing characters that are in the input list. Stop when you encounter a character not in the list. In this case that means that &lt;code&gt;rengew wer&lt;/code&gt; are removed (right to left) and then the &lt;code&gt;d&lt;/code&gt; in &lt;code&gt;andrew&lt;/code&gt; is encountered so that &lt;code&gt;rstrip&lt;/code&gt; function stops. &lt;/li&gt;
&lt;li&gt;Return the remaining string of &lt;code&gt;and&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="solution"&gt;Solution&lt;a class="headerlink" href="#solution" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Python has two functions that will correctly remove a &lt;strong&gt;string&lt;/strong&gt; - &lt;a href="https://docs.python.org/3.10/library/stdtypes.html#str.removesuffix"&gt;&lt;code&gt;.removesuffix()&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://docs.python.org/3.10/library/stdtypes.html#str.removeprefix"&gt;&lt;code&gt;.removeprefix()&lt;/code&gt;&lt;/a&gt; for right and left side removals. &lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; "Andrew Wegner".lower().removesuffix(" wegner")
'andrew'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;These two functions were introduced in Python 3.9 as part of &lt;a href="https://peps.python.org/pep-0616/"&gt;PEP-616&lt;/a&gt;. In the PEP, it explicitly calls out the confusion users have about the &lt;code&gt;*strip()&lt;/code&gt; functions and how they behave. These two were introduced to allow the desired behavior. &lt;/p&gt;
&lt;p&gt;One important note is that these two &lt;code&gt;remove*&lt;/code&gt; functions will only remove &lt;em&gt;at most&lt;/em&gt; one instance of the string.&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; "Andrew Wegner Wegner".lower().removesuffix(" wegner")
'andrew wegner'
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</content><category term="Technical Solutions"/><category term="technical"/></entry><entry><title>Installing the Mobile Security Framework (MobSF) on Windows using Docker</title><link href="https://andrewwegner.com/install-mobsf-on-windows-docker.html" rel="alternate"/><published>2024-02-19T15:00:00-06:00</published><updated>2024-02-19T15:00:00-06:00</updated><author><name>Andy Wegner</name></author><id>tag:andrewwegner.com,2024-02-19:/install-mobsf-on-windows-docker.html</id><summary type="html">&lt;p&gt;A short article on installing the Mobile Security Framework on Windows utilizing Docker and then using it to quickly analyze an APK.&lt;/p&gt;</summary><content type="html">
&lt;h2 id="introduction"&gt;Introduction&lt;a class="headerlink" href="#introduction" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I recently needed to poke around an APK. I'd done this sort of thing before, but it's been years so the process of setting up an Android environment was unfamilar. Instead of spending a day reimmersing myself for what was (hopefully) going to be a 10-15 minute thing, I turned to another tool: &lt;a href="https://github.com/MobSF/Mobile-Security-Framework-MobSF"&gt;Mobile Security Framework&lt;/a&gt; (also known as MobSF)&lt;/p&gt;
&lt;h2 id="setting-up-docker"&gt;Setting up Docker&lt;a class="headerlink" href="#setting-up-docker" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are countless blog posts, articles, and YouTube videos on how to set up the perfect docker environment. This was a quick task. I didn't need "perfect", I needed good enough. At the time, I was on a Windows 11 machine, so that's what I had to work with. The goal was to &lt;a href="https://docs.docker.com/get-docker/"&gt;get Docker&lt;/a&gt; operational as quickly as possible.&lt;/p&gt;
&lt;p&gt;Why? MobSF has a quick setup with two lines of Docker code. &lt;/p&gt;
&lt;p&gt;Using the &lt;a href="https://docs.docker.com/desktop/install/windows-install/"&gt;Install Docker for Windows&lt;/a&gt; documentation, I downloaded the installable and started the process. The installation took a couple minutes on the device I was on and required me to log out and back in. Total install time was less than 5 minutes.&lt;/p&gt;
&lt;h2 id="installing-mobsf"&gt;Installing MobSF&lt;a class="headerlink" href="#installing-mobsf" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Once logged back in, I fired up the command line. The &lt;a href="https://github.com/MobSF/Mobile-Security-Framework-MobSF"&gt;MobSF README&lt;/a&gt; provides two lines to execute on the command line to pull the latest image. I didn't go through the process of setting up a dymanic analyzer. I didn't think I'd need it (and as luck would have it in this case, I was correct).&lt;/p&gt;
&lt;p&gt;Run the two commands:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;docker pull opensecurity/mobile-security-framework-mobsf:latest
docker run -it --rm -p 8000:8000 opensecurity/mobile-security-framework-mobsf:latest&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This will pull the latest version of MobSF and then run it on port 8000 on the local machine. Once it's running you can access it in your browser by going to &lt;code&gt;http://127.0.0.1:8000&lt;/code&gt;&lt;/p&gt;
&lt;h2 id="find-your-apk"&gt;Find your APK&lt;a class="headerlink" href="#find-your-apk" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Theorectically, you'll be running this against an APK you've built and developed. In that case, you'll have the APK handy and can load it into the UI. I, unfortunately, didn't have that luxuary. Fortunately, getting the APK is relatively simple.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Go to the &lt;a href="https://play.google.com/store/apps"&gt;Google Playstore&lt;/a&gt; and search for the application&lt;/li&gt;
&lt;li&gt;Navigate into the details page of the application (you should see the option to install it)&lt;/li&gt;
&lt;li&gt;Copy the URL to this page&lt;/li&gt;
&lt;li&gt;Go to &lt;a href="https://apkcombo.com/downloader/"&gt;APKCombo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Paste the URL you copied in step 3 instal the appropriate text box and click "Generate Download Link"&lt;/li&gt;
&lt;li&gt;After a few seconds, click the download icon next to the link that was generated&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You now have the APK to use.&lt;/p&gt;
&lt;h2 id="using-mobsf"&gt;Using MobSF&lt;a class="headerlink" href="#using-mobsf" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With the APK in hand, and MobSF running, navigate to &lt;code&gt;http://127.0.0.1:8000&lt;/code&gt;. Upload the APK and wait a couple of minutes. If you are watching the logs, you'll see it performing multiple steps behind the scenes. Once it's done analyzing the APK, it'll drop you to the dashboard.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Analyzing an APK" src="https://andrewwegner.com/images/mobsf/analyze.png"/&gt;&lt;/p&gt;
&lt;p&gt;&lt;img alt="Analyzing an APK with logs" src="https://andrewwegner.com/images/mobsf/analyze-logs.png"/&gt;&lt;/p&gt;
&lt;p&gt;The dashboard has a &lt;em&gt;lot&lt;/em&gt; of detail about the APK. Scroll through to see the permissions it's utilizing, certificates attached to it, potential security vulnerabilities, code analysis, detected URLs in the code, hard coded secrets, and strings. All of these would help a team build a better and more secure mobile application. &lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;For my purpose, this 15 minute excersice not only answered the question I was seeking it also raised a few items that will need to be corrected before the next release. Introducing security practices with a tool like MobSF would be incredibly benefitial to a team tasked with building an Android application. Further, adding in the dynamic analysis capabilities can provide more details on areas to improve. &lt;/p&gt;
&lt;p&gt;While I didn't need those to answer my question, I'd enourage someone looking to introduce this to a team, to look at the &lt;a href="https://mobsf.github.io/docs/#/dynamic_analyzer"&gt;capabilities of MobSF&lt;/a&gt;&lt;/p&gt;</content><category term="Technical Solutions"/><category term="technical"/></entry><entry><title>Python Gotcha: Modifying a list while iterating</title><link href="https://andrewwegner.com/python-gotcha-modify-list-while-iterating.html" rel="alternate"/><published>2024-01-03T15:00:00-06:00</published><updated>2024-01-03T15:00:00-06:00</updated><author><name>Andy Wegner</name></author><id>tag:andrewwegner.com,2024-01-03:/python-gotcha-modify-list-while-iterating.html</id><summary type="html">&lt;p&gt;Python makes it easy to modify a list while you are iterating through it's elements. This will bite you. Read on to find out how and what can be done about it.&lt;/p&gt;</summary><content type="html">
&lt;h2 id="introduction"&gt;Introduction&lt;a class="headerlink" href="#introduction" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A fairly common task an engineer may need to do is to remove all numbers from a list below a threshold. It's a simple enough task that anyone could accomplish - iterate over the list, check the value, if it's below the threshold remove it. With Python you can do this in a &lt;code&gt;for&lt;/code&gt; loop.&lt;/p&gt;
&lt;h2 id="gotcha"&gt;Gotcha&lt;a class="headerlink" href="#gotcha" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Let's check code for this simple problem.&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;numbers_list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;0, 9, 2, 1, 5, 1, 10, 4, 3&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;enumerate&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="err"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Deleted'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'Kept'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;del&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numbers_list&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;print&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;It'd be reasonable to expect an final list that looks like this:&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;[9, 5, 10]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But, the actual output is:&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;[9, 1, 5, 10, 3]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;What is &lt;em&gt;happening&lt;/em&gt;? &lt;/p&gt;
&lt;h2 id="explaination"&gt;Explaination&lt;a class="headerlink" href="#explaination" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The (unconsious?) assumption that the developer is making with this piece of code is that the final list &lt;em&gt;must&lt;/em&gt; be in &lt;code&gt;numbers_list&lt;/code&gt;. Since we're able to iterate and modify, there is no need for a temporary variable or a need to worry about &lt;a href="https://andrewwegner.com/python-gotcha-list-copy.html"&gt;list copy gotchas&lt;/a&gt;. But, let's go through this one iteration at a time.&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="nv"&gt;Starting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;list&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;[&lt;span class="mi"&gt;0&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;]

&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Iteration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Index&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;?&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Action&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;List&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;after&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;iteration&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;---------&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-----&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-----&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;----------&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;------&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-------------------------&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;True&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Delete&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;[&lt;span class="mi"&gt;9&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;]&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;True&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Delete&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;[&lt;span class="mi"&gt;9&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;]&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;False&lt;/span&gt;&lt;span class="w"&gt;      &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Keep&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;[&lt;span class="mi"&gt;9&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;]&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;True&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Delete&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;[&lt;span class="mi"&gt;9&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;]&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt;         &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="w"&gt;     &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;True&lt;/span&gt;&lt;span class="w"&gt;       &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Delete&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;[&lt;span class="mi"&gt;9&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;]&lt;span class="w"&gt;          &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;

&lt;span class="k"&gt;End&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;iteration&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;list&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;has&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;no&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;more&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;elements&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Looking at this, it's a little clearer what's going on. Let's go through there one iteration at a time.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;On the first iteration, the value of &lt;code&gt;Index 0&lt;/code&gt; is &lt;code&gt;0&lt;/code&gt;, so it's deleted. &lt;em&gt;And the problem starts now&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;On the next iteration, the value of &lt;code&gt;Index 1&lt;/code&gt; is &lt;code&gt;2&lt;/code&gt; (&lt;strong&gt;not 9&lt;/strong&gt;). In the previous iteration, &lt;code&gt;Index 0&lt;/code&gt; was deleted, which means everything has moved up one index. The value of &lt;code&gt;9&lt;/code&gt; was never checked. Instead, we look at a value of &lt;code&gt;2&lt;/code&gt;. It's less than our threshold so it's also deleted.&lt;/li&gt;
&lt;li&gt;On the third iteration, the value of &lt;code&gt;Index 2&lt;/code&gt; is &lt;code&gt;5&lt;/code&gt;. Again, we've skipped a value (&lt;code&gt;1&lt;/code&gt;) and it's never even checked. This iteration doesn't result in a deletion because &lt;code&gt;5&lt;/code&gt; is not less than &lt;code&gt;5&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;On the forth iteration, the value of &lt;code&gt;Index 3&lt;/code&gt; is &lt;code&gt;1&lt;/code&gt;. This is deleted as expected.&lt;/li&gt;
&lt;li&gt;On the fifth iteration, the value of &lt;code&gt;Index 4&lt;/code&gt; is &lt;code&gt;4&lt;/code&gt;. We've skipped another value (&lt;code&gt;10&lt;/code&gt;) and this iteration's value is less than our threshold so it's deleted. &lt;/li&gt;
&lt;li&gt;There is no further iteration, because the last one has made the final value the last index, so the final value is never checked. &lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We skipped checking several values because we messed with indexes that were either being operated on or hadn't been operated on yet. This results in unexpected behavior.&lt;/p&gt;
&lt;h2 id="solution"&gt;Solution&lt;a class="headerlink" href="#solution" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A quick solution is to add the values that exceed our threshold to another variable:&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;numbers_list&lt;/span&gt;&lt;span class="w"&gt; &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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&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;larger_list&lt;/span&gt;&lt;span class="w"&gt; &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="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;enumerate&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="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;larger_list&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&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="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;larger_list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This results in the expected list of &lt;code&gt;[9, 5, 10]&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Another solution, assuming that the result &lt;em&gt;needs&lt;/em&gt; to sit in a variable with the same name, is to use a list comprehension. This does the same thing as above, but keeps the code more compact. It also works because the final result is assigned to a new variable object with the same name.&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;numbers_list = [0, 9, 2, 1, 5, 1, 10, 4, 3]
numbers_list = [nums for nums in numbers_list if nums &amp;gt;= 5]
print(numbers_list)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This results in the expected list of &lt;code&gt;[9, 5, 10]&lt;/code&gt;&lt;/p&gt;</content><category term="Technical Solutions"/><category term="technical"/></entry><entry><title>Python Gotcha: List Copy Problems</title><link href="https://andrewwegner.com/python-gotcha-list-copy.html" rel="alternate"/><published>2023-12-20T15:30:00-06:00</published><updated>2023-12-20T15:30:00-06:00</updated><author><name>Andy Wegner</name></author><id>tag:andrewwegner.com,2023-12-20:/python-gotcha-list-copy.html</id><summary type="html">&lt;p&gt;Copying a Python list (or any mutable object) isn't as simple as setting one equal to another. Let's discuss a better way to do this.&lt;/p&gt;</summary><content type="html">
&lt;h2 id="introduction"&gt;Introduction&lt;a class="headerlink" href="#introduction" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This problem isn't limited to only &lt;code&gt;list&lt;/code&gt;s. Any mutable object in Python can be impacted by this "gotcha". The problem is setting one mutable object equal to another doesn't make a copy. It assigns a new variable to the same object as the original. Let's take a look and see how this appears.&lt;/p&gt;
&lt;h2 id="the-first-gotcha"&gt;The First Gotcha&lt;a class="headerlink" href="#the-first-gotcha" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The first example is pretty simple. Let's make a copy of our list&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; list1 = [1,2,3,4,5]
&amp;gt;&amp;gt;&amp;gt; list2 = list1
&amp;gt;&amp;gt;&amp;gt; list1
[1, 2, 3, 4, 5]
&amp;gt;&amp;gt;&amp;gt; list2
[1, 2, 3, 4, 5]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That looks like I have two lists, both with the same set of values. But, let's modify the second list and change the first element in the list to be &lt;code&gt;6&lt;/code&gt;&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; list2
[6, 2, 3, 4, 5]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But what about &lt;code&gt;list1&lt;/code&gt;? It also has changed.&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; list1
[6, 2, 3, 4, 5]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is occuring because, as mentioned, &lt;code&gt;list2&lt;/code&gt; is assigned to the same object as &lt;code&gt;list1&lt;/code&gt;. Any change to either, changes the object that both are assigned to.&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; print(f"list1 id: {id(list1)}")
list1 id: 1497534800448
&amp;gt;&amp;gt;&amp;gt; print(f"list2 id: {id(list2)}")
list2 id: 1497534800448
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="the-first-solution"&gt;The First Solution&lt;a class="headerlink" href="#the-first-solution" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This can be solved a few different ways. The important take away, though, is that you have to create a new object with the original values. I can think of a few ways to handle this. This article doesn't consider performance for these basic lists.&lt;/p&gt;
&lt;p&gt;One option is to utilize the &lt;code&gt;list()&lt;/code&gt; method and pass it the initial values from &lt;code&gt;list1&lt;/code&gt;&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; list3 = list(list1)
&amp;gt;&amp;gt;&amp;gt; list3
[6, 2, 3, 4, 5]
&amp;gt;&amp;gt;&amp;gt; list3[0] = 0
&amp;gt;&amp;gt;&amp;gt; list3
[0, 2, 3, 4, 5]
&amp;gt;&amp;gt;&amp;gt; list1
[6, 2, 3, 4, 5]
&amp;gt;&amp;gt;&amp;gt; print(f"list1 id: {id(list1)}")
list1 id: 1497534800448
&amp;gt;&amp;gt;&amp;gt; print(f"list3 id: {id(list3)}")
list3 id: 1497531282368
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;A second option is to &lt;code&gt;slice()&lt;/code&gt; the entire original list into a new variable.&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; list4 = list1[:]
&amp;gt;&amp;gt;&amp;gt; list4
[6, 2, 3, 4, 5]
&amp;gt;&amp;gt;&amp;gt; list4[0] = 0
&amp;gt;&amp;gt;&amp;gt; list4
[0, 2, 3, 4, 5]
&amp;gt;&amp;gt;&amp;gt; list1
[6, 2, 3, 4, 5]
&amp;gt;&amp;gt;&amp;gt; print(f"list1 id: {id(list1)}")
list1 id: 1497534800448
&amp;gt;&amp;gt;&amp;gt; print(f"list4 id: {id(list4)}")
list4 id: 1497534798208
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Lastly, the &lt;code&gt;list&lt;/code&gt; object has a &lt;code&gt;copy()&lt;/code&gt; method that you can utilize. &lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; list5 = list1.copy()
&amp;gt;&amp;gt;&amp;gt; list5[0] = 0
&amp;gt;&amp;gt;&amp;gt; list5
[0, 2, 3, 4, 5]
&amp;gt;&amp;gt;&amp;gt; list1
[6, 2, 3, 4, 5]
&amp;gt;&amp;gt;&amp;gt; print(f"list1 id: {id(list1)}")
list1 id: 1497534800448
&amp;gt;&amp;gt;&amp;gt; print(f"list5 id: {id(list5)}")
list5 id: 1497534799552
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;With each of these three options, you can see that we created new objects because each has a unique object id. Changing one of these copies does not change the original's values.&lt;/p&gt;
&lt;h2 id="the-second-gotcha"&gt;The Second Gotcha&lt;a class="headerlink" href="#the-second-gotcha" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You, a savvy developer, know all this though. You also have a more advanced data structure. You have a &lt;em&gt;list of lists&lt;/em&gt;. &lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;[
    ['first element', 1, 2, 3],
    ['second element', 4, 5, 6],
    ['third element', 7, 8, 9],
]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Since you know about this gotcha, you are going to make a new object and copy the values to the new object.&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; deeplist1 = [
...     ['first element', 1, 2, 3],
...     ['second element', 4, 5, 6],
...     ['third element', 7, 8, 9],
... ]
&amp;gt;&amp;gt;&amp;gt; deeplist2 = deeplist1.copy()
&amp;gt;&amp;gt;&amp;gt; print(f"deeplist1 id: {id(deeplist1)}")
deeplist1 id: 1497534571008
&amp;gt;&amp;gt;&amp;gt; print(f"deeplist2 id: {id(deeplist2)}")
deeplist2 id: 1497534577536
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now, with two different objects, is it safe to modify an element in one of the sublists? Turns out, it is not.&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; deeplist2[0][0] = "NOT 3RD"
&amp;gt;&amp;gt;&amp;gt; deeplist2
[['NOT 3RD', 1, 2, 3], ['second element', 4, 5, 6], ['third element', 7, 8, 9]]
&amp;gt;&amp;gt;&amp;gt; deeplist1
[['NOT 3RD', 1, 2, 3], ['second element', 4, 5, 6], ['third element', 7, 8, 9]]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;But why? The ids of &lt;code&gt;deeplist1&lt;/code&gt; and &lt;code&gt;deeplist2&lt;/code&gt; are different. This is true, but the sublists are not. Each sublist is assigned the same object id as the original.&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; id(deeplist1[0][0]), id(deeplist2[0][0])
(1497534796864, 1497534796864)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="the-second-solution"&gt;The Second Solution&lt;a class="headerlink" href="#the-second-solution" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Utilize the &lt;a href="https://docs.python.org/3/library/copy.html#copy.deepcopy"&gt;&lt;code&gt;deepcopy()&lt;/code&gt;&lt;/a&gt; method to make a copy of your original object.&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;copy&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;deeplist3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;copy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deepcopy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deeplist1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;deeplist3&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;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"NEW VALUE"&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;deeplist3&lt;/span&gt;
&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s1"&gt;'NEW VALUE'&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="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'second element'&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="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'third element'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&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="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;deeplist1&lt;/span&gt;
&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="s1"&gt;'NOT 3RD'&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="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'second element'&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="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'third element'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Using &lt;code&gt;deepcopy()&lt;/code&gt; we can safely make a copy of our list of lists and modify both the original and the copy without impacting the other.&lt;/p&gt;</content><category term="Technical Solutions"/><category term="technical"/></entry><entry><title>Python Gotcha: Timezone Naive Functions like utcnow() and utcfromtimestamp()</title><link href="https://andrewwegner.com/python-gotcha-timezone-naive-functions.html" rel="alternate"/><published>2023-11-23T10:15:00-06:00</published><updated>2023-11-23T10:15:00-06:00</updated><author><name>Andy Wegner</name></author><id>tag:andrewwegner.com,2023-11-23:/python-gotcha-timezone-naive-functions.html</id><summary type="html">&lt;p&gt;utcnow() and utcfromtimestamp() don't know about timezones and that causes problems. Let's talk about those, how to fix the problem and their recent deprecation in Python 3.12&lt;/p&gt;</summary><content type="html">
&lt;h2 id="introduction"&gt;Introduction&lt;a class="headerlink" href="#introduction" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There are two common functions in that are &lt;a href="https://docs.python.org/3/library/datetime.html#aware-and-naive-objects"&gt;naive datetime objects&lt;/a&gt;. The behind these types of objects is that timezones don't matter. Perhaps you always assume that datetimes are in a set timezone. Or, you are aware of timezones and want to make things easy on yourself so always assume datetimes are set to UTC.&lt;/p&gt;
&lt;p&gt;This is your mistake. &lt;/p&gt;
&lt;p&gt;The &lt;code&gt;utcnow()&lt;/code&gt; and &lt;code&gt;utctimestamp()&lt;/code&gt; functions are naive, but other &lt;code&gt;datetime&lt;/code&gt; functions are not.&lt;/p&gt;
&lt;h2 id="the-gotcha"&gt;The Gotcha&lt;a class="headerlink" href="#the-gotcha" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;datetime&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utcfromtimestamp&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;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt;
&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1970&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;1&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;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="mf"&gt;21600.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;What happened here? &lt;/p&gt;
&lt;p&gt;This was run on a computer in the Central Standard Time time zone. First, I created an object set to the UNIX epoch - Jan. 1, 1970. Since I utilized &lt;code&gt;utcfromtimestamp()&lt;/code&gt; to do this, there is no timezone data attached to this object. When I use the standard &lt;code&gt;timestamp()&lt;/code&gt; function, Python sees there is no timestamp to utilize, so it uses the system configured timezone. Hence the 21,600 second offset from the epoch.&lt;/p&gt;
&lt;p&gt;This didn't always happen though. Sometime &lt;a href="https://github.com/python/cpython/issues/81669"&gt;between Python 2 and Python 3 this behavior changed&lt;/a&gt;. This assumption that "no timezone means local timezone" introduced this gotcha. In my opinion isn't a rather dangerous one too.&lt;/p&gt;
&lt;h2 id="solution"&gt;Solution&lt;a class="headerlink" href="#solution" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Utilize explicit time stamps everywhere. From &lt;a href="https://peps.python.org/pep-0020/"&gt;The Zen of Python - PEP 20&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Explicit is better than implicit. &lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;datetime&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fromtimestamp&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;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTC&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt;
&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1970&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;1&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;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="mf"&gt;0.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Notice that by adding &lt;code&gt;datetime.UTC&lt;/code&gt; to my &lt;code&gt;fromtimestamp()&lt;/code&gt; call, I get a datetime object with &lt;code&gt;tzinfo&lt;/code&gt;. This makes the conversion from the datetime object back into ta timestamp safe. &lt;/p&gt;
&lt;p&gt;This is the way that Python is moving. In an effort to correct these unsafe assumptions that were introduced early in Python 3, the naive datetime functions have been deprecated. If you were to run my first block of code in Python 3.12, you receive a deprecation warning. &lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;dt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;datetime&lt;/span&gt;.&lt;span class="nv"&gt;utcfromtimestamp&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;stdin&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;:&lt;span class="mi"&gt;1&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;DeprecationWarning&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;datetime&lt;/span&gt;.&lt;span class="nv"&gt;datetime&lt;/span&gt;.&lt;span class="nv"&gt;utcfromtimestamp&lt;/span&gt;&lt;span class="ss"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;deprecated&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;scheduled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;removal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;future&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;version&lt;/span&gt;.&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;Use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;timezone&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="nv"&gt;aware&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;objects&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;represent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;datetimes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;UTC&lt;/span&gt;:&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;datetime&lt;/span&gt;.&lt;span class="nv"&gt;datetime&lt;/span&gt;.&lt;span class="nv"&gt;fromtimestamp&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;timestamp&lt;/span&gt;,&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;datetime&lt;/span&gt;.&lt;span class="nv"&gt;UTC&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Notice that the message is telling you to add the timezone information and to utilize &lt;code&gt;fromtimestamp()&lt;/code&gt;. In the coming year - years? - both the commonly seen &lt;code&gt;utcnow()&lt;/code&gt; amd &lt;code&gt;utcfromtimestamp()&lt;/code&gt; will be removed.&lt;/p&gt;
&lt;p&gt;In my opinion, as someone that has fought this bug in the past, it's a welcome change. Timezone assumptions are difficult to troubleshoot. Assumptions about underlying behavior make it a challenge. Running the same script in different timezones, like you would with a geographicly diverse team, makes it even harder. It's especially annoying if one of your coworkers is utilizing UTC as their time zone so that the script behaves properly for them but no others.&lt;/p&gt;
&lt;p&gt;I have code bases that will need to make this change, but in the end, they will be easier to maintain with the removal of the &lt;code&gt;utc*&lt;/code&gt; functions.&lt;/p&gt;</content><category term="Technical Solutions"/><category term="technical"/></entry><entry><title>Ruby Gotcha: Operator Precedence</title><link href="https://andrewwegner.com/ruby-gotcha-operator-precedence.html" rel="alternate"/><published>2023-10-23T10:45:00-05:00</published><updated>2023-10-23T10:45:00-05:00</updated><author><name>Andy Wegner</name></author><id>tag:andrewwegner.com,2023-10-23:/ruby-gotcha-operator-precedence.html</id><summary type="html">&lt;p&gt;In Ruby you can use either &lt;code&gt;and&lt;/code&gt; or &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;. You can use &lt;code&gt;or&lt;/code&gt; or &lt;code&gt;||&lt;/code&gt;. What's is the difference and what's the catch?&lt;/p&gt;</summary><content type="html">
&lt;h2 id="introduction"&gt;Introduction&lt;a class="headerlink" href="#introduction" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In one of my previous roles, the team utilized Ruby. I was not an individual contributor at that point in my career, so my exposure to it was less than much of my team. However, during my tenure, I noticed several "Gotchas" of the Ruby language. Each language has these in their own way, but in this post I'm going to cover one specific one.&lt;/p&gt;
&lt;p&gt;What's the difference between &lt;code&gt;and&lt;/code&gt; and &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;? What about the difference between &lt;code&gt;or&lt;/code&gt; and &lt;code&gt;||&lt;/code&gt;?&lt;/p&gt;
&lt;h2 id="the-gotcha"&gt;The Gotcha&lt;a class="headerlink" href="#the-gotcha" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At first pass, these two blocks of code appear to do the same thing. Let's take a look.&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;irb(main):003:0&amp;gt; true &amp;amp;&amp;amp; false
=&amp;gt; false
irb(main):004:0&amp;gt; true and false
=&amp;gt; false
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;By the rules of boolean logic, both &lt;code&gt;and&lt;/code&gt; and &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; statements return &lt;code&gt;false&lt;/code&gt;.&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;irb(main):005:0&amp;gt; true || false
=&amp;gt; true
irb(main):006:0&amp;gt; true or false
=&amp;gt; true
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Boolean logic holds true for the &lt;code&gt;or&lt;/code&gt; and &lt;code&gt;||&lt;/code&gt; statements and return &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;But, what happens if we assign the result of this to a variable?&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;irb(main):007:0&amp;gt; result = true and false
=&amp;gt; false
irb(main):008:0&amp;gt; result1 = true &amp;amp;&amp;amp; false
=&amp;gt; false
irb(main):009:0&amp;gt; result
=&amp;gt; true
irb(main):010:0&amp;gt; result1
=&amp;gt; false
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Now we see the problem. With the &lt;code&gt;and&lt;/code&gt; operator, &lt;code&gt;result = true&lt;/code&gt;. With the &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; operator, &lt;code&gt;result1 = false&lt;/code&gt;. &lt;/p&gt;
&lt;p&gt;Looking at the &lt;code&gt;or&lt;/code&gt;/&lt;code&gt;||&lt;/code&gt; operators quickly,&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;irb(main):011:0&amp;gt; result2 = true or false
=&amp;gt; true
irb(main):012:0&amp;gt; result3 = true || false
=&amp;gt; true
irb(main):013:0&amp;gt; result2
=&amp;gt; true
irb(main):014:0&amp;gt; result3
=&amp;gt; true
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This all looks reasonable. But, let's swap the order of our &lt;code&gt;true&lt;/code&gt;/&lt;code&gt;false&lt;/code&gt; in the statement.&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;irb(main):015:0&amp;gt; result2 = false or true
=&amp;gt; true
irb(main):016:0&amp;gt; result3 = false || true
=&amp;gt; true
irb(main):017:0&amp;gt; result2
=&amp;gt; false
irb(main):018:0&amp;gt; result3
=&amp;gt; true
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here we see the differences again, with the &lt;code&gt;or&lt;/code&gt; operator setting &lt;code&gt;result2 = false&lt;/code&gt; and the &lt;code&gt;||&lt;/code&gt; operator setting &lt;code&gt;result3 = true&lt;/code&gt;&lt;/p&gt;
&lt;h2 id="what-is-going-on"&gt;What is going on?&lt;a class="headerlink" href="#what-is-going-on" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;and&lt;/code&gt; and &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; / &lt;code&gt;or&lt;/code&gt; and &lt;code&gt;||&lt;/code&gt; are the same thing, but they have different orders of precedence. The (not well formatted in my opinion) documentation shows the &lt;a href="https://docs.ruby-lang.org/en/3.2/syntax/precedence_rdoc.html"&gt;precedence table for Ruby&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The key things to point out here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; and &lt;code&gt;||&lt;/code&gt; are above &lt;code&gt;and&lt;/code&gt; and &lt;code&gt;or&lt;/code&gt;, meaning they will be evaluated first.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;=&lt;/code&gt; is also above &lt;code&gt;and&lt;/code&gt; and &lt;code&gt;or&lt;/code&gt; (but below &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; and &lt;code&gt;||&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let's look at one of the examples again:&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;irb(main):015:0&amp;gt; result2 = false or true
=&amp;gt; true
irb(main):017:0&amp;gt; result2
=&amp;gt; false
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;With the order of precedence in mind, this is what happens:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;result2&lt;/code&gt; is assigned the value of &lt;code&gt;false&lt;/code&gt; because &lt;code&gt;=&lt;/code&gt; occurs before &lt;code&gt;or&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;The remainder of the statement is evaluated as &lt;code&gt;true&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This was hidden when &lt;code&gt;true&lt;/code&gt; and &lt;code&gt;false&lt;/code&gt; was swapped because &lt;code&gt;result2&lt;/code&gt; was assigned a value of &lt;code&gt;true&lt;/code&gt; and then the remainder of the statement was also evaluated to &lt;code&gt;true&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="how-do-you-prevent-this"&gt;How do you prevent this?&lt;a class="headerlink" href="#how-do-you-prevent-this" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There is very few reasons to utilize the English words &lt;code&gt;and&lt;/code&gt; and &lt;code&gt;or&lt;/code&gt; in Ruby's logical evaluations. Instead, these should be utilized as flow control modifiers (think &lt;code&gt;if&lt;/code&gt; and &lt;code&gt;unless&lt;/code&gt;) and not for boolean logic. &lt;/p&gt;
&lt;p&gt;Here's an example of how it should be utilized for flow control&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mi"&gt;019&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;numeral&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;numeral&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;': undefined method `/'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;NilClass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;NoMethodError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Ruby32&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;x64&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ruby&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mf"&gt;3.2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gems&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1.6&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="n"&gt;exe&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;top&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;required&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Ruby32&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;x64&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;33&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="nb"&gt;load&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Ruby32&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;x64&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;bin&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;33&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;irb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;&lt;span class="mi"&gt;020&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;numeral&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;numeral&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;&lt;span class="w"&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;In our first example, with &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt;, the statement is broken up like this&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;numeral = (10 &amp;amp;&amp;amp; numeral) / 5
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This won't work because &lt;code&gt;numeral&lt;/code&gt; doesn't have a value when &lt;code&gt;&amp;amp;&amp;amp;&lt;/code&gt; is evaluated. Contrast this with the second version where the code:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Assigns a value of &lt;code&gt;10&lt;/code&gt; to &lt;code&gt;numeral&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;Divides &lt;code&gt;numeral&lt;/code&gt; by &lt;code&gt;5&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;While basic, it should get the point across. Another example could be something like this:&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;user = User.find_by_email(email) and user.send_email!
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here, we are only sending an email to the user if we find their information. No information, no email is sent. &lt;/p&gt;</content><category term="Technical Solutions"/><category term="technical"/></entry><entry><title>Python Gotcha: Comparisons</title><link href="https://andrewwegner.com/python-gotcha-comparisons.html" rel="alternate"/><published>2023-10-18T10:15:00-05:00</published><updated>2023-10-18T10:15:00-05:00</updated><author><name>Andy Wegner</name></author><id>tag:andrewwegner.com,2023-10-18:/python-gotcha-comparisons.html</id><summary type="html">&lt;p&gt;Comparing two numerical variables in Python can have surprising results if you aren't aware of some common gotchas. This post covers a couple of the common ones.&lt;/p&gt;</summary><content type="html">
&lt;h2 id="float-equality-comparisons"&gt;Float Equality Comparisons&lt;a class="headerlink" href="#float-equality-comparisons" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Like every other programming language, Python can't accurately represent floating-point numbers. I'm sure many Computer Science students have lost many hours learning how &lt;a href="https://www.doc.ic.ac.uk/~eedwards/compsys/float/"&gt;floating representation works&lt;/a&gt;. I remember that class well.&lt;/p&gt;
&lt;p&gt;In any case, let's get into the problem with comparing &lt;code&gt;float&lt;/code&gt; values in Python and how to handle it.&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; 0.1 + 0.2 == 0.3
False
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You, me, and anyone with a few years of elementary school under their belt can see that this &lt;em&gt;should be&lt;/em&gt; &lt;code&gt;True&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;What's happening here? We can see the problem by breaking down the component parts of this comparison.&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; 0.3
0.3
&amp;gt;&amp;gt;&amp;gt; 0.1 + 0.2
0.30000000000000004
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;And now we see the floating point representation that is causing a problem.&lt;/p&gt;
&lt;p&gt;So, how do we deal with this?&lt;/p&gt;
&lt;h3 id="decimal"&gt;Decimal&lt;a class="headerlink" href="#decimal" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;There are a couple options, both of which have their drawbacks. Let's start with &lt;a href="https://docs.python.org/3/library/decimal.html"&gt;&lt;code&gt;Decimal&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The decimal module provides support for fast correctly rounded decimal floating point arithmetic.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This sounds good, but an important gotcha here too is how it handles numerals vs. strings.&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;decimal&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="p"&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="n"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'0.1000000000000000055511151231257827021181583404541015625'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'0.1'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'0.1'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This means, to accomplish the comparison above, we need to wrap each of our numbers in a string.&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; Decimal('0.1') + Decimal('0.2') == Decimal('0.3')
True
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That's annoying, but does function. &lt;/p&gt;
&lt;h3 id="isclose"&gt;isclose&lt;a class="headerlink" href="#isclose" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Python 3.5 implemented &lt;a href="https://peps.python.org/pep-0485/"&gt;PEP 485&lt;/a&gt; to test for approximate equality. This is done in the &lt;a href="https://docs.python.org/3/library/math.html#math.isclose"&gt;&lt;code&gt;isclose&lt;/code&gt;&lt;/a&gt; function. &lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;math&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isclose&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="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kc"&gt;True&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;That's cleaner than wrapping everything in strings. But, it also is more verbose than a simple &lt;code&gt;==&lt;/code&gt; statement. It makes your code less clean, but does provide accurate comparisons.&lt;/p&gt;
&lt;h2 id="is-vs"&gt;is vs. ==&lt;a class="headerlink" href="#is-vs" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Another comparison that I've commonly seen misapplied is developers using &lt;code&gt;is&lt;/code&gt; when they mean &lt;code&gt;==&lt;/code&gt;. Put simply, &lt;code&gt;is&lt;/code&gt; should ONLY be used if you are checking if two references refer to the same object. &lt;code&gt;==&lt;/code&gt; is used to compare value by calling underlying &lt;code&gt;__eq__&lt;/code&gt; methods.&lt;/p&gt;
&lt;p&gt;Let's see this in action:&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; a = [1, 2, 3]
&amp;gt;&amp;gt;&amp;gt; b = a
&amp;gt;&amp;gt;&amp;gt; c = [1, 2, 3]
&amp;gt;&amp;gt;&amp;gt; d = [3, 2, 1]
&amp;gt;&amp;gt;&amp;gt; a == b
True
&amp;gt;&amp;gt;&amp;gt; a == c
True
&amp;gt;&amp;gt;&amp;gt; a == d
False
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;So far, nothing unusual. &lt;code&gt;a&lt;/code&gt; has the same values of &lt;code&gt;b&lt;/code&gt; and &lt;code&gt;c&lt;/code&gt; and different values from &lt;code&gt;d&lt;/code&gt;. Now let's use &lt;code&gt;is&lt;/code&gt;&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; a is b
True
&amp;gt;&amp;gt;&amp;gt; b is a
True
&amp;gt;&amp;gt;&amp;gt; a is c
False
&amp;gt;&amp;gt;&amp;gt; a is d
False
&amp;gt;&amp;gt;&amp;gt; b is c
False
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Here, the only &lt;code&gt;True&lt;/code&gt; statements are the comparison between &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt;. This is because &lt;code&gt;b&lt;/code&gt; was initialized with the statement &lt;code&gt;b = a&lt;/code&gt;. The other two variables were initialized as their own statements and values. &lt;strong&gt;Remember, &lt;code&gt;is&lt;/code&gt; compares object references. If they are the same, it returned &lt;code&gt;True&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; id(a), id(b), id(c), id(d)
(2267170738432, 2267170738432, 2267170545600, 2267170359040)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Since &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; are the same object, we get a &lt;code&gt;True&lt;/code&gt; on their comparison. The others are different, hence the &lt;code&gt;False&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="nan-nan"&gt;nan == nan&lt;a class="headerlink" href="#nan-nan" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;nan&lt;/code&gt;, or Not a Number, is a floating point value that can not be converted to anything other than a float and is considered not equal to all other values. It's a common way to represent missing values in a data set.&lt;/p&gt;
&lt;p&gt;There is a key phrase in that description above that is the basis for this Gotcha:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;is considered not equal to all other values&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It's common for software to check if two values are equal to one another prior to taking an action. For &lt;code&gt;nan&lt;/code&gt;, that does not work:&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; float('nan') == float('nan')
False
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This prevents code like this from entering the &lt;code&gt;if&lt;/code&gt; block of an &lt;code&gt;if/else&lt;/code&gt; statement&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;float&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'nan'&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;b&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;float&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'nan'&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;b&lt;/span&gt;:
...&lt;span class="w"&gt;   &lt;/span&gt;..&lt;span class="w"&gt; &lt;/span&gt;##&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;Do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;something&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;equal&lt;/span&gt;
...&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;:
...&lt;span class="w"&gt;   &lt;/span&gt;..&lt;span class="w"&gt; &lt;/span&gt;##&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;Do&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;something&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;equal&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;In this example, they are &lt;em&gt;never&lt;/em&gt; equal. &lt;/p&gt;
&lt;p&gt;This leads to an interesting, if not unintutive, way of checking if a variable is a &lt;code&gt;nan&lt;/code&gt; value. Since &lt;code&gt;nan&lt;/code&gt; is not equal to &lt;em&gt;all&lt;/em&gt; other values, it is not equal to itself.&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; a != a
True
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Like I said, "Interesting". But, when your code is looked at by others it's also "confusing". There is an easier way to show that you are checking for a &lt;code&gt;nan&lt;/code&gt; value. &lt;a href="https://docs.python.org/3/library/math.html#math.isnan"&gt;&lt;code&gt;isnan&lt;/code&gt;&lt;/a&gt;&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;math&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'nan'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'infinity'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isnan&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="kc"&gt;True&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isnan&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="kc"&gt;False&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isnan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kc"&gt;False&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;To me, that's a much clearer check that we want to see if the value is &lt;code&gt;nan&lt;/code&gt;. It's likely you aren't just passing &lt;code&gt;nan&lt;/code&gt; to a single variable. You're probably using a library like &lt;a href="https://numpy.org/"&gt;NumPy&lt;/a&gt; or &lt;a href="https://pandas.pydata.org/"&gt;Pandas&lt;/a&gt;. In that case, you have functions in each of those libraries that can check for &lt;code&gt;nan&lt;/code&gt; in a performant way.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In NumPy the function has the same name but in the NumPy library: &lt;code&gt;numpy.isnan(value)&lt;/code&gt;. &lt;/li&gt;
&lt;li&gt;In Pandas, the function has a slightly different name: &lt;code&gt;pandas.isna(value)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="conclusion"&gt;Conclusion&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Comparisons aren't always as straight forward as we'd like. I covered a few common comparison problems in Python here. &lt;/p&gt;
&lt;p&gt;Floating point comparisons are common across languages. Python has a few ways of making this easier for developers. I recommend utilizing &lt;code&gt;isclose&lt;/code&gt; as it keeps the code a bit cleaner and eliminates the need to wrap numbers in strings if using the &lt;code&gt;Decimal&lt;/code&gt; module.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;is&lt;/code&gt; should &lt;em&gt;only&lt;/em&gt; being used to check if two items are referring to the same object. In any other case, it's not doing the check you want it to be doing. &lt;/p&gt;
&lt;p&gt;Lastly, &lt;code&gt;nan&lt;/code&gt; is equal to &lt;em&gt;nothing else&lt;/em&gt;. It's important to be aware of that before you start comparing values in your dataset to one another. &lt;/p&gt;</content><category term="Technical Solutions"/><category term="technical"/></entry><entry><title>Python Gotcha: Join vs Concat</title><link href="https://andrewwegner.com/python-gotcha-join-vs-concat.html" rel="alternate"/><published>2023-10-12T10:45:00-05:00</published><updated>2023-10-12T10:45:00-05:00</updated><author><name>Andy Wegner</name></author><id>tag:andrewwegner.com,2023-10-12:/python-gotcha-join-vs-concat.html</id><summary type="html">&lt;p&gt;Append 100,000 strings together using join or concat - Which is faster?&lt;/p&gt;</summary><content type="html">
&lt;h2 id="the-problem"&gt;The Problem&lt;a class="headerlink" href="#the-problem" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Here's a somewhat contrivied example to demonstrate a problem. I need to create a string with the word &lt;code&gt;word&lt;/code&gt; 100,000 times. What's the fastest way to generate this string? I could use string concatenation (simply &lt;code&gt;+&lt;/code&gt; the strings to one another). I could &lt;a href="https://docs.python.org/3/library/stdtypes.html#str.join"&gt;&lt;code&gt;join&lt;/code&gt;&lt;/a&gt; a list with 100,000 items. &lt;/p&gt;
&lt;p&gt;My proposed code for this example is below&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;concat_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;final_string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;""&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &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;iterations&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;final_string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;final_string&lt;/span&gt;

&lt;span class="n"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;join_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;iterations&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;final_string&lt;/span&gt;&lt;span class="w"&gt; &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="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &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;iterations&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;final_string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&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="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&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;final_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="results"&gt;Results&lt;a class="headerlink" href="#results" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In &lt;code&gt;concat_string&lt;/code&gt;, I iterate 100,000 items with each iteration adding my &lt;code&gt;word&lt;/code&gt; to the end of the string. In &lt;code&gt;join_string&lt;/code&gt;, I append my &lt;code&gt;word&lt;/code&gt; to a list on each iteration and then join the list into a string at the end. &lt;/p&gt;
&lt;p&gt;Running each function through the built in profiler (&lt;code&gt;cProfile&lt;/code&gt;) shows how these two functions perform.&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; cProfile.run('concat_string("word ")')

      4 function calls in 1.026 seconds

Ordered by: standard name

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     1    0.000    0.000    1.026    1.026 &amp;lt;string&amp;gt;:1(&amp;lt;module&amp;gt;)
     1    1.026    1.026    1.026    1.026 test.py:9(concat_string)
     1    0.000    0.000    1.026    1.026 {built-in method builtins.exec}
     1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}

&amp;gt;&amp;gt;&amp;gt; cProfile.run('join_string("word ")')

      100005 function calls in 0.013 seconds

Ordered by: standard name

ncalls  tottime  percall  cumtime  percall filename:lineno(function)
     1    0.000    0.000    0.013    0.013 &amp;lt;string&amp;gt;:1(&amp;lt;module&amp;gt;)
     1    0.009    0.009    0.013    0.013 test.py:16(join_string)
     1    0.000    0.000    0.013    0.013 {built-in method builtins.exec}
100000    0.004    0.000    0.004    0.000 {method 'append' of 'list' objects}
     1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
     1    0.000    0.000    0.000    0.000 {method 'join' of 'str' objects}
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="whats-happening-here"&gt;What's happening here?&lt;a class="headerlink" href="#whats-happening-here" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;join&lt;/code&gt; is more than 75x than the concatination method. Why?&lt;/p&gt;
&lt;p&gt;String are immutable objects in Python. I talked about these in my last &lt;a href="https://andrewwegner.com/python-gotcha-optional-default-arguments.html"&gt;Gotcha Article about default parameters&lt;/a&gt;. This immutability means that a string can't be changed. &lt;code&gt;concat_string&lt;/code&gt; does appear to be changing the string with each &lt;code&gt;+&lt;/code&gt; action, but under the hood, Python has to create a new string object each iteration through the loop. That means there are 99,999 temporary string values - creating and discarding almost all of them immediately on the next iteration during the concatenation action. &lt;/p&gt;
&lt;p&gt;&lt;code&gt;join_string&lt;/code&gt; on the other hand, is appending 100,000 string objects to a list. But, only one list is created. The final &lt;code&gt;join&lt;/code&gt; is only doing a &lt;em&gt;single&lt;/em&gt; concatenation with all 100,000 strings.&lt;/p&gt;
&lt;h2 id="what-are-the-implications-of-this"&gt;What are the implications of this?&lt;a class="headerlink" href="#what-are-the-implications-of-this" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While this is a contrived example to show the problem, there are real performance impacts to string immutability that may not be obvious. There are other places where a new string is created commonly used in Python. A couple examples are &lt;code&gt;f-strings&lt;/code&gt;, &lt;code&gt;%s&lt;/code&gt; format specifiers and &lt;code&gt;.format()&lt;/code&gt;. Each of these create a brand new string. &lt;/p&gt;
&lt;p&gt;This doesn't mean you should avoid these, as the performance impact is only really obvious in situations where you are appending &lt;em&gt;a lot&lt;/em&gt; of strings together. However, if you have a string formatting line in a loop, it's a potential area to focus on for performance improvements.&lt;/p&gt;</content><category term="Technical Solutions"/><category term="technical"/></entry><entry><title>Python Gotcha: Mutable Default Optional Arguments</title><link href="https://andrewwegner.com/python-gotcha-optional-default-arguments.html" rel="alternate"/><published>2023-10-06T10:15:00-05:00</published><updated>2023-10-06T10:15:00-05:00</updated><author><name>Andy Wegner</name></author><id>tag:andrewwegner.com,2023-10-06:/python-gotcha-optional-default-arguments.html</id><summary type="html">&lt;p&gt;A quick walk through of why mutable defaults on optional arguments is bad in Python.&lt;/p&gt;</summary><content type="html">
&lt;h2 id="the-problem"&gt;The Problem&lt;a class="headerlink" href="#the-problem" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Without running this in your IDE, what does the following code output? Forgive the use of &lt;code&gt;vins&lt;/code&gt;, I work in the logistics field and deal with vehicle identification numbers frequently.&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;def add_vin(this_vin: str, vins: list = []):
    if this_vin not in vins:
        vins.append(this_vin)
    return vins

add_vin("VIN1")
add_vin("VIN2")
print(add_vin("VIN3"))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;You probably saw that &lt;code&gt;vins&lt;/code&gt; is an optional argument and that it defaults to an empty list. Run this function three times without passing it a list to append to, and it's reasonable to assume that you have a final result of a list with a single item contained in it - &lt;code&gt;VIN3&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Unfortunately, that's wrong. The output is actually &lt;code&gt;["VIN1", "VIN2", "VIN3"]&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Why?&lt;/p&gt;
&lt;h2 id="whats-going-on"&gt;What's going on?&lt;a class="headerlink" href="#whats-going-on" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In Python, default arguments are bound to a function at the time the function is defined. This is only done once. If you don't pass the optional argument in to the function call, the same list is changed when the function is called. This means that in the example above, a new list is not created each time the function is called without the optional &lt;code&gt;vins&lt;/code&gt; argument.&lt;/p&gt;
&lt;p&gt;We can see this by slightly modifying the code above to show the object ID we are operating on.&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;def add_vin(this_vin, vins=[]):
    if this_vin not in vins:
        print(f"id={id(vins)}")    # Print the object ID we are appending to
        vins.append(this_vin)
    return vins
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Which outputs the following as it adds each VIN.&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;id=2954990867712
id=2954990867712
id=2954990867712
['VIN1', 'VIN2', 'VIN3']
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Each time &lt;code&gt;add_vin&lt;/code&gt; was called without passing it a list to operate on, the function will operate on the same default list.&lt;/p&gt;
&lt;h2 id="another-example"&gt;Another example&lt;a class="headerlink" href="#another-example" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The example above is basic, but gets the point across. However, let's look at something a bit more common. Connecting to a database with a connection object at the parameter.&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;open_database&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;connection&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;make_connection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'example.com'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;# do database things with `connection`&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;connection&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Well done. You've closed your connection. Your DBA will thank you. However, what happens next time you call &lt;code&gt;open_database()&lt;/code&gt; in your code? After all, your connection information is there by default, you don't need to pass a new connection, right?&lt;/p&gt;
&lt;p&gt;Again, wrong. You've closed the &lt;code&gt;connection&lt;/code&gt; and it's the default argument. The next call to &lt;code&gt;open_database&lt;/code&gt; will utilize the same &lt;code&gt;connection&lt;/code&gt; object. A connection that is closed. Your database call will fail.&lt;/p&gt;
&lt;h2 id="last-example"&gt;Last Example&lt;a class="headerlink" href="#last-example" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;One last example:&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;time&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;print_datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()):&lt;/span&gt;
    &lt;span class="k"&gt;return&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;dt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;print_datetime&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&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="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;print_datetime&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is going to print the current datetime, sleep for 10 seconds, and print the new datetime - right?! &lt;/p&gt;
&lt;p&gt;No. &lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="mf"&gt;2023&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;10&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;06&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;08&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;45&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;21.392973&lt;/span&gt;
&lt;span class="mf"&gt;2023&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;10&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;06&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;08&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;45&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;21.392973&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Why?! &lt;/p&gt;
&lt;p&gt;Again, an optional default argument is only bound once. Since a new value isn't passed to the function on the second call, the exact same value is used the second time through. Whoops. &lt;/p&gt;
&lt;h2 id="what-can-i-do-about-this"&gt;What can I do about this?&lt;a class="headerlink" href="#what-can-i-do-about-this" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The best option, in my opinion, is to set the default values to &lt;code&gt;None&lt;/code&gt;. This is going to make it very obvious that there is a problem. In the first example, you'll get an exception because you can't append to &lt;code&gt;None&lt;/code&gt;. In the second, your database calls will error because &lt;code&gt;None&lt;/code&gt; doesn't contain connection information. In the third, you'll print &lt;code&gt;None&lt;/code&gt; instead of an expected &lt;code&gt;datatime&lt;/code&gt;. &lt;/p&gt;
&lt;p&gt;You could pass an immutable object like a tuple or a frozenset, but even these have caveats to be aware of. For example, a tuple can contain something mutable in it (ie. a list). Personally, I prefer to utilize &lt;code&gt;None&lt;/code&gt;.&lt;/p&gt;</content><category term="Technical Solutions"/><category term="technical"/></entry><entry><title>Syncing Obsidian Notes across devices using Git and GitLab</title><link href="https://andrewwegner.com/obsidian-gitlab-setup.html" rel="alternate"/><published>2023-09-26T13:30:00-05:00</published><updated>2023-09-26T13:30:00-05:00</updated><author><name>Andy Wegner</name></author><id>tag:andrewwegner.com,2023-09-26:/obsidian-gitlab-setup.html</id><summary type="html">&lt;p&gt;Setting up Obsidian to sync notes across devices utilizing git and a GitLab backend&lt;/p&gt;</summary><content type="html">
&lt;h2 id="introduction"&gt;Introduction&lt;a class="headerlink" href="#introduction" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I take a lot of notes. About five years ago I switched from utilizing notebooks, because I was going through them so quickly, to a 
&lt;a href="https://getrocketbook.com/"&gt;Rocket Book&lt;/a&gt;. This feels very similar to a notebook, with the benefit that it is reusable. It also has an application that allows me to quickly 
scan a page and save it to one of several locations. It's very handy. The downside is that the OCR isn't perfect (oooor, my handwriting is bad) and the
annoyance of cleaning the pages every week or so. I've found that if I don't let the pages dry completely, then the next time I go to use that page it 
has a fine film over it that makes writing a little more difficult. Think writing with a pen that is low on ink.&lt;/p&gt;
&lt;p&gt;I discovered &lt;a href="https://getrocketbook.com/"&gt;Obsidian&lt;/a&gt; a while ago. I've put a little bit of effort into utilizing it, but more recently I've committed to utilizing it more.
The idea of all digial notes is enticing. Over time I'll see if it's helpful.&lt;/p&gt;
&lt;p&gt;But, like anyone with multiple devices, I want my Obsidian vault on all devices. What if I have a thought while I'm away from my main computer? One option is to utilize the &lt;a href="https://obsidian.md/sync"&gt;Obsidian Sync&lt;/a&gt; feature. It's got end to end encryption, version history and a monthly subscription fee. I can build something using my &lt;a href="https://andrewwegner.com/installing-gitlab.html"&gt;existing GitLab installation&lt;/a&gt; without that monthly subscription fee. Plus, since I'm the only user of this, I'm ok with a little trade off that a full service provides. &lt;/p&gt;
&lt;h2 id="repository-setup"&gt;Repository Setup&lt;a class="headerlink" href="#repository-setup" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="gitlab"&gt;GitLab&lt;a class="headerlink" href="#gitlab" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Within GitLab set up a new project by clicking on "New Project" in the upper right.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Create a new GitLab Project" src="https://andrewwegner.com/images/obsidian/new_gitlab_project.png"/&gt;&lt;/p&gt;
&lt;p&gt;Select the option to create a new blank project.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Create a blank repository" src="https://andrewwegner.com/images/obsidian/blank_gitlab_project.png"/&gt;&lt;/p&gt;
&lt;p&gt;Now we need to set up the project details. Select a name that is meaningful for you. I've utilized &lt;code&gt;andy-notes&lt;/code&gt; in the screenshot below. I also created this without a &lt;code&gt;README&lt;/code&gt;, because I'll generate the initial repository in a moment and push it up to the server.&lt;/p&gt;
&lt;p&gt;&lt;img alt="New project details - without a precreated README" src="https://andrewwegner.com/images/obsidian/project_details.png"/&gt;&lt;/p&gt;
&lt;h3 id="repository-initialization"&gt;Repository Initialization&lt;a class="headerlink" href="#repository-initialization" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This set of steps only has to occur once. All devices will be able to start with the next section.&lt;/p&gt;
&lt;p&gt;Open the command prompt or terminal (depending on what your operating system calls the system console). Navigate to the location you want your notes to be stored. In my case, I created a new directory to match the name of my repository. I then created a &lt;code&gt;.gitignore&lt;/code&gt; with the following content in the directory.&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="na"&gt;.obsidian&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="p"&gt;*&lt;/span&gt;
&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="na"&gt;.obsidian&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="no"&gt;app.json&lt;/span&gt;
&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="na"&gt;.obsidian&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="no"&gt;appearance.json&lt;/span&gt;
&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="na"&gt;.obsidian&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="no"&gt;config&lt;/span&gt;
&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="na"&gt;.obsidian&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="no"&gt;community-plugins.json&lt;/span&gt;
&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="na"&gt;.obsidian&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="no"&gt;core-plugins.json&lt;/span&gt;
&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="na"&gt;.obsidian&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="no"&gt;graph.json&lt;/span&gt;
&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="na"&gt;.obsidian&lt;/span&gt;&lt;span class="err"&gt;/&lt;/span&gt;&lt;span class="no"&gt;hotkeys.json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This should ignore the majority of the &lt;code&gt;.obsidian&lt;/code&gt; hidden directory between my devices, but keep a few key items. I may update this over time, but for now this is working well.&lt;/p&gt;
&lt;p&gt;Now, initialize the repository.&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;cd andy-notes
git init --initial-branch=main
git remote add origin git@&amp;lt;my.server.url&amp;gt;:andy/andy-notes.git
git add .
git commit -m "Initial Commit"
git push --set-upstream origin main
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;We're ready to start utilizing Obsidian and GitLab!&lt;/p&gt;
&lt;h2 id="obsidian-setup"&gt;Obsidian Setup&lt;a class="headerlink" href="#obsidian-setup" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="first-open"&gt;First Open&lt;a class="headerlink" href="#first-open" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Now that the backend repository is set up and it's been initially configured, all instances of Obsidian can utilize these sets of steps to start working. &lt;/p&gt;
&lt;p&gt;If this is not the device you initilized the repository on, you need to perform an initial &lt;code&gt;pull&lt;/code&gt; of the repository to this device so that it is a local folder.&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;git clone git@&amp;lt;my.server.url&amp;gt;:andy/andy-notes.git
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Open Obsidian. Since this is the first time you are opening it, you'll be asked to select where your vault is stored. You want to select the "Open folder as vault" option.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Open an existing folder as your vault" src="https://andrewwegner.com/images/obsidian/open_folder.png"/&gt;&lt;/p&gt;
&lt;p&gt;Select the location that your repository has been cloned into. &lt;/p&gt;
&lt;h3 id="enable-plugins"&gt;Enable Plugins&lt;a class="headerlink" href="#enable-plugins" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Obsidian has support for &lt;a href="https://obsidian.md/plugins"&gt;community built plugins&lt;/a&gt;. There are plugins to do all kinds of things to improve your writing and note taking. I encourage the reader to look through or search for plugins that may help you. We, however, need one specific one to accomplish our goal of syncing the vault over to GitLab.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://obsidian.md/plugins?id=obsidian-git"&gt;Obsidian Git&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;To install community plugins we need to enable this feature. In the lower left of the workspace, click the gears icon. Then click on "Community plugins", and finally "Turn on community plugins"&lt;/p&gt;
&lt;p&gt;&lt;img alt="Enable Community Plugins" src="https://andrewwegner.com/images/obsidian/enable_plugins.png"/&gt;&lt;/p&gt;
&lt;p&gt;With this enabled, you can now browse the community plugins. To do so, click the browse button.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Browse community plugins button" src="https://andrewwegner.com/images/obsidian/browse_plugins.png"/&gt;&lt;/p&gt;
&lt;p&gt;Search for &lt;code&gt;Obsidian Git&lt;/code&gt;. While writing this, it was the first result. You should be looking for the one by Vinzent. Click on the card to pull up plugin details and potential actions.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Obsidian Git in the Community Plugins marketplace" src="https://andrewwegner.com/images/obsidian/plugin_search.png"/&gt;&lt;/p&gt;
&lt;p&gt;Then click "Install". The installation process should only take a moment or two.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Install the Obsidian Git plugin by clicking the install button" src="https://andrewwegner.com/images/obsidian/plugin_install.png"/&gt;&lt;/p&gt;
&lt;p&gt;You'll receive a confirmation in the upper right if the install is successful.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Successful plugin install confirmation message" src="https://andrewwegner.com/images/obsidian/plugin_install_success.png"/&gt;&lt;/p&gt;
&lt;p&gt;Finally, enable the new plugin. The previous "install" button has changed to be "Enable". Click that.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Enable new Obsidian Git plugin" src="https://andrewwegner.com/images/obsidian/plugin_enable.png"/&gt;&lt;/p&gt;
&lt;h3 id="configure-obsidian-git"&gt;Configure Obsidian Git&lt;a class="headerlink" href="#configure-obsidian-git" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;With the Obsidian Git plugin installed, it's time to configure it. There are a few quick settings to fill out. First, you need to click the "Options" button.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Obsidian Git plugin configuration options" src="https://andrewwegner.com/images/obsidian/plugin_options.png"/&gt;&lt;/p&gt;
&lt;p&gt;The first setting to change is how often you want the backup to GitLab to occur. This automatic back up is disabled by default. I set mine to be every 5 minutes. &lt;/p&gt;
&lt;p&gt;&lt;img alt="Adjust the backup interval" src="https://andrewwegner.com/images/obsidian/backup_interval.png"/&gt;&lt;/p&gt;
&lt;p&gt;Next, enable &lt;code&gt;Pull updates on startup&lt;/code&gt; so that the instance of Obsidian that is running is always up to date. Your notes from other devices will be synchronized when Obsidian launches and you can get right back to writing.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Enable pull updates on startup with this toggle" src="https://andrewwegner.com/images/obsidian/pull_startup.png"/&gt;&lt;/p&gt;
&lt;p&gt;Be sure to add the Author name and email address. This is the information that will be sent on each commit. &lt;/p&gt;
&lt;p&gt;&lt;img alt="Commit Author information" src="https://andrewwegner.com/images/obsidian/commit_author.png"/&gt;&lt;/p&gt;
&lt;p&gt;I also adjusted the autocommit message to include the hostname of the device performing the commit. I have been going back and forth on whether this is important enough for me to keep since I'm the only one that utilizes this vault. But, if you want to adjust the messages there are two instances where the commit message can be modified.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Adding the hostname to the automatically generated commit messages" src="https://andrewwegner.com/images/obsidian/commit_author.png"/&gt;&lt;/p&gt;
&lt;h2 id="results"&gt;Results&lt;a class="headerlink" href="#results" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After performing the steps above, this instance of Obsidian is ready to utilize GitLab to sync your vault for free. Each new instance only needs to have the Obsidian Git plugin installed, enabled and configured. &lt;/p&gt;
&lt;p&gt;After you've configured your instance, when you open Obsidian the plugin will pull changes from the repository and let you know that everything has been updated.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Obsidian successfully updated the vault after opening" src="https://andrewwegner.com/images/obsidian/open_success.png"/&gt;&lt;/p&gt;
&lt;p&gt;Then on a scheduled basis - 5 minutes in my case - it will commit and push changes to GitLab.&lt;/p&gt;
&lt;p&gt;&lt;img alt="Successful autocommit" src="https://andrewwegner.com/images/obsidian/autocommit.png"/&gt;&lt;/p&gt;
&lt;p&gt;In GitLab, this commit will should show up immediately. &lt;/p&gt;
&lt;p&gt;&lt;img alt="GitLab commit" src="https://andrewwegner.com/images/obsidian/commit.png"/&gt;&lt;/p&gt;
&lt;h2 id="conclusion"&gt;Conclusion&lt;a class="headerlink" href="#conclusion" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I now have Obsidian set up on my main devices. I've enjoyed the ability to have my notes available as soon as I open the application. Overall, this was pretty easy to set up. It seems like a lot of steps, but the one time setup makes it much longer than installing and configuring a plugin for each new instance actually takes.&lt;/p&gt;
&lt;p&gt;I'm trying to keep my notes digital and I'm hopeful that this new set up will help. I can move between computers for personal projects instead of wondering what I had found in my research because I left it on another tab on another machine and forgot to put it with the rest of my notes. Now, it just happens. &lt;/p&gt;</content><category term="Technical Solutions"/><category term="technical"/><category term="gitlab"/></entry><entry><title>How to disable Grafana in Gitlab 16.3 Omnibus</title><link href="https://andrewwegner.com/disable-grafana-in-gitlab-16.html" rel="alternate"/><published>2023-08-23T22:15:00-05:00</published><updated>2024-07-07T00:00:00-05:00</updated><author><name>Andy Wegner</name></author><id>tag:andrewwegner.com,2023-08-23:/disable-grafana-in-gitlab-16.html</id><summary type="html">&lt;p&gt;GitLab 16.3 deprecated and disabled the bundled Grafana, but didn't provide complete instructions for how to disable it. Fortunately, it's easy to do. I've documented the few steps needed.&lt;/p&gt;</summary><content type="html">
&lt;h2 id="introduction"&gt;Introduction&lt;a class="headerlink" href="#introduction" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;I continue to love my &lt;a href="https://andrewwegner.com/installing-gitlab.html"&gt;self hosted instance of GitLab&lt;/a&gt;. I look forward to the 22nd of every month to pull down the 
newest dot update and enjoy the new features. As with any piece of software, things get deprecated and eventually disabled over
time. &lt;/p&gt;
&lt;p&gt;In 16.3 - released in August 2023 - the version of &lt;a href="https://grafana.com/"&gt;Grafana&lt;/a&gt; bundled with the self hosted version of GitLab reached the 
end of it's depreciation window and was disabled. GitLab added a check to prevent an update if it was still enabled. &lt;/p&gt;
&lt;h2 id="symptom"&gt;Symptom&lt;a class="headerlink" href="#symptom" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;When performing an &lt;code&gt;apt&lt;/code&gt; update, GitLab 16.3 will error out if Grafana is still enabled on your instance. The error looks like this:&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="n"&gt;Preparing&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;unpack&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;gitlab&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;ee_16&lt;/span&gt;&lt;span class="mf"&gt;.3.0&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;ee&lt;/span&gt;&lt;span class="mf"&gt;.0&lt;/span&gt;&lt;span class="n"&gt;_amd64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deb&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;
&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;grafana&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;enable&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;has&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;been&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;deprecated&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;since&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;16.0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;was&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;removed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;16.3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;The&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bundled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Grafana&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;deprecated&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;no&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;longer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;available&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;We&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;recommond&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;following&lt;/span&gt;
&lt;span class="nl"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="o"&gt;//&lt;/span&gt;&lt;span class="n"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gitlab&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ee&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;administration&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;monitoring&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;performance&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;grafana_configuration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;html#deprecation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;Deprecations&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;found&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Please&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;correct&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;them&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;again&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;h2 id="the-problem"&gt;The Problem&lt;a class="headerlink" href="#the-problem" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The &lt;a href="https://docs.gitlab.com/ee/administration/monitoring/performance/grafana_configuration.html#deprecation"&gt;documentation linked&lt;/a&gt; provides four steps to follow&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;To switch away from bundled Grafana to a newer version of Grafana from Grafana Labs:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Set up a version of Grafana from Grafana Labs.&lt;/li&gt;
&lt;li&gt;Export the existing dashboards from bundled Grafana.&lt;/li&gt;
&lt;li&gt;Import the existing dashboards in the new Grafana instance.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.gitlab.com/ee/administration/monitoring/performance/grafana_configuration.html#integrate-with-gitlab-ui"&gt;Configure GitLab&lt;/a&gt; to use the new Grafana instance.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;I've removed the links in steps 2 and 3, because they really don't matter for this write up. Step 1 does not have a link, so it's left as 
and excercise to the user to install Grafana in their environment. Step 4, links to another article that provides a few more steps to follow.&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;On the left sidebar, expand the top-most chevron.&lt;/li&gt;
&lt;li&gt;Select Admin Area.&lt;/li&gt;
&lt;li&gt;On the left sidebar, select Settings &amp;gt; Metrics and profiling and expand Metrics - Grafana.&lt;/li&gt;
&lt;li&gt;Select the Add a link to Grafana checkbox.&lt;/li&gt;
&lt;li&gt;Configure the Grafana URL. Enter the full URL of the Grafana instance.&lt;/li&gt;
&lt;li&gt;Select Save changes.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;And that's it. That's the set of instructions provided by GitLab. Follow those, rerun your &lt;code&gt;apt&lt;/code&gt; update and you'll run into the same symptom.&lt;/p&gt;
&lt;h2 id="solution"&gt;Solution&lt;a class="headerlink" href="#solution" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The missing step is hinted at in the error message itself: &lt;code&gt;grafana[enable] has been deprecated&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This is a setting in the GitLab configuration file. You will need to edit the config file and set the value to false.&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Edit &lt;code&gt;/etc/gitlab/gitlab.rb&lt;/code&gt; (or appropriate path) &lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Edit the &lt;code&gt;grafana[enable]&lt;/code&gt; value to be &lt;code&gt;false&lt;/code&gt;. On my config this was on line 1689, but I recommend you search for the string &lt;code&gt;grafana&lt;/code&gt; to find it. &lt;/p&gt;
&lt;p&gt;&lt;code&gt;grafana['enable'] = false&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Reconfigure GitLab by running &lt;code&gt;gitlab-ctl reconfigure&lt;/code&gt;. Let this run and it should end with &lt;code&gt;gitlab Reconfigured!&lt;/code&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;You can now resume the &lt;code&gt;apt&lt;/code&gt; update that failed due to having Grafana enabled.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;Once done, you will get the standard GitLab upgrade complete message and you'll be good to go.&lt;/p&gt;
&lt;h2 id="reading-unsupported-config-value-grafana"&gt;Reading unsupported config value grafana.&lt;a class="headerlink" href="#reading-unsupported-config-value-grafana" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;Update: July 2024, GitLab 17.1&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In the GitLab 17.1 update from June 2024, Grafana has been completely removed. If you still have &lt;code&gt;grafana&lt;/code&gt; keys in the config file upon updating to 17.1, the upgrade will fail with&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;Mixlib::config::unknownconfigoptionerror: Reading unsupported config value grafana.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;The solution to this is to edit &lt;code&gt;/etc/gitlab/gitlab.rb&lt;/code&gt; and comment out (or remove entirely) all lines that start with &lt;code&gt;grafana&lt;/code&gt;. Once this is done, save the file and attempt the upgrade again. Like when it was originally deprecated, this step will end with &lt;code&gt;gitlab Reconfigured&lt;/code&gt;.&lt;/p&gt;</content><category term="Technical Solutions"/><category term="technical"/><category term="gitlab"/></entry><entry><title>CoderByte assessments fall to ChatGPT</title><link href="https://andrewwegner.com/chatgpt-beats-more-interview-assessments.html" rel="alternate"/><published>2023-02-04T23:30:00-06:00</published><updated>2023-02-04T23:30:00-06:00</updated><author><name>Andy Wegner</name></author><id>tag:andrewwegner.com,2023-02-04:/chatgpt-beats-more-interview-assessments.html</id><summary type="html">&lt;p&gt;CoderByte falls to ChatGPT just like the more well known interview assessment platforms. Here's my run down, and how I used ChatGPT to solve an easy, medium and hard problem in less than 5 minutes total.&lt;/p&gt;</summary><content type="html">
&lt;h2 id="introduction"&gt;Introduction&lt;a class="headerlink" href="#introduction" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Today I'll be evaluating &lt;a href="https://coderbyte.com/developers"&gt;CoderByte&lt;/a&gt; and their publicly posted challenges against ChatGPT. I selected an easy, a medium and the single hard
question from their list of &lt;a href="https://coderbyte.com/challenges"&gt;open challenges&lt;/a&gt; and ran those through ChatGPT.&lt;/p&gt;
&lt;p&gt;In previous articles in this series, I've shown how &lt;a href="https://openai.com/blog/chatgpt/"&gt;ChatGPT&lt;/a&gt; can easily solve interview assessments from &lt;a href="https://andrewwegner.com/chatgpt-should-end-leetcode-interviews.html"&gt;LeetCode&lt;/a&gt;, 
&lt;a href="https://andrewwegner.com/breaking-the-interview-with-chatgpt.html"&gt;TestGorilla&lt;/a&gt;, &lt;a href="https://andrewwegner.com/chatgpt-breaks-more-interview-questions.html"&gt;CodeSignal&lt;/a&gt;, &lt;a href="https://andrewwegner.com/chatgpt-continues-beating-interview-questions.html"&gt;Codility&lt;/a&gt; and &lt;a href="https://andrewwegner.com/solving-more-interview-questions-with-chatgpt.html"&gt;HackerRank&lt;/a&gt;[hackerrankg]. I've said it in 
those articles, but I'll say it again: Companies need to evaluate their engineering candidates - all candidates, actually - apprpriately 
when they are hiring. Your engineers are using many tools to do their jobs and AI Tooling is one of the newest tools on the market. Removing 
a tool from your engineers is going to make them less efficient, and &lt;em&gt;not&lt;/em&gt; testing your candidates on how they use these tools is 
going to mean you hire someone that had not fully demonstrated their capabilities.&lt;/p&gt;
&lt;p&gt;ChatGPT should be treated as a tool, but it's also helpful to think of it as a junior level engineer. You need to check it's work. 
&lt;a href="https://andrewwegner.com/chatgpt-interact-with-external-api.html"&gt;ChatGPT is known to be inaccurate&lt;/a&gt;, which is why it remains &lt;a href="https://andrewwegner.com/stackoverflow-bans-chatgpt.html"&gt;banned on Stack Overflow&lt;/a&gt;. There are a few instances where
I had to do that below.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;First a disclaimer&lt;/em&gt;: I was Director of Engineering at &lt;a href="https://www.woventeams.com/"&gt;Woven Teams&lt;/a&gt; in 2022. Prior to that, 
&lt;a href="https://andrewwegner.com/woven-client-to-woven-employee.html"&gt;I was a customer of Woven for over two years&lt;/a&gt;. I do not remember CoderByte being mentioned during my tenure and CoderByte does not 
seem to consider Woven a competitor, as Woven isn't listed as an &lt;a href="https://coderbyte.com/organizations#pricing"&gt;alternative on their pricing FAQ page&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="easy-react-button-toggle"&gt;Easy - React Button Toggle&lt;a class="headerlink" href="#easy-react-button-toggle" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While I've tried to be mostly random in my selection of assessments during these tests, I intentionally selected this react 
assessment because I don't know react. I've liked these types of assessment questions in the previous articles because I means I can't 
apply my experience to "nudge" ChatGPT toward a solution. &lt;/p&gt;
&lt;p&gt;The challenge here is to toggle a button between "On" and "Off". You're presented with a very small amount of boiler plate code. &lt;/p&gt;
&lt;p&gt;I simply copy/pasted the prompt and the template into ChatGPT. It sent back this:&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;React&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="n"&gt;useState&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'react'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;ReactDOM&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'react-dom'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="n"&gt;function&lt;/span&gt; &lt;span class="n"&gt;Toggle&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="n"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;setToggle&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;true&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handleClick&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;setToggle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;!&lt;/span&gt;&lt;span class="n"&gt;toggle&lt;/span&gt;&lt;span class="p"&gt;);&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="n"&gt;onClick&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;handleClick&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;toggle&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;'ON'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'OFF'&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;button&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;ReactDOM&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;render&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;Toggle&lt;/span&gt; &lt;span class="o"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="n"&gt;document&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'root'&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;I copied that into the IDE provided by CoderByte and submitted the code.&lt;/p&gt;
&lt;p&gt;&lt;img alt="CoderByte - Easy Challenge - React Toggle Button Score" src="https://andrewwegner.com/images/coderbyte-easy-tests.png"/&gt;&lt;/p&gt;
&lt;p&gt;This test took two minutes to complete. The bulk of that time was spot checking that ChatGPT didn't mess with the boiler plate code to badly.&lt;/p&gt;
&lt;p&gt;&lt;img alt="CoderByte - Easy Challenge - React Toggle Button Time Taken" src="https://andrewwegner.com/images/coderbyte-easy-minutes.png"/&gt;&lt;/p&gt;
&lt;p&gt;The interesting this about the results is that even with the 10/10, the system says it only scored higher than 12% of other users. Does this mean
that only 12% of the users passing this assessment are getting lower than a 10/10? To me, that's the sign of a bad assessment.&lt;/p&gt;
&lt;p&gt;&lt;img alt="CoderByte - Easy Challenge - React Toggle Button Score comparison" src="https://andrewwegner.com/images/coderbyte-easy-scores.png"/&gt;&lt;/p&gt;
&lt;h2 id="medium-sql-member-count"&gt;Medium - SQL Member Count&lt;a class="headerlink" href="#medium-sql-member-count" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The medium challenge I selected was an SQL question. SQL is interesting, in my experience, in that the results returned on an assessment are a 
lot more uniform compared to a non-SQL coding assessment. This one wasn't a difficult one with joins.&lt;/p&gt;
&lt;p&gt;The goal of this assessment was to return a list of names in alphabetical order, a count of the number of people that report to them, and the 
average age of their team members. I pasted the prompt, the expected table layout for the output and the table layout being selected from.&lt;/p&gt;
&lt;p&gt;The first response was &lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ReportsTo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Members&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;AVG&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="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Average_Age&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;maintable_YTTQH&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ReportsTo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;NULL&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ReportsTo&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ReportsTo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;This is where experience, and checking the results are important. The expected output showed that the average age should be an integer. It also showed that the average age column should be named &lt;code&gt;Average Age&lt;/code&gt; not &lt;code&gt;Average_Age&lt;/code&gt; (notice the lack of the underscore). I provided this guidance to ChatGPT&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The AVG(Age) should be an integer not a float and The "Average_Age" field should be "Average Age" without an underscore. &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It corrected both problems with a new query which I submitted. &lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ReportsTo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;COUNT&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Members&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;ROUND&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;AVG&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="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Average Age"&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;maintable_YTTQH&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ReportsTo&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;IS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;NOT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="no"&gt;NULL&lt;/span&gt;
&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ReportsTo&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ReportsTo&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;&lt;img alt="CoderByte - Medium Challenge - SQL Score" src="https://andrewwegner.com/images/coderbyte-medium-tests.png"/&gt;&lt;/p&gt;
&lt;p&gt;ChatGPT scored another 10/10. &lt;/p&gt;
&lt;p&gt;&lt;img alt="CoderByte - Medium Challenge - SQL Time Taken" src="https://andrewwegner.com/images/coderbyte-medium-minutes.png"/&gt;&lt;/p&gt;
&lt;p&gt;Even with that extra round trip with ChatGPT, it took less than a minute to complete this assessment. &lt;/p&gt;
&lt;p&gt;&lt;img alt="CoderByte - Medium Challenge - SQL Score Comparison" src="https://andrewwegner.com/images/coderbyte-medium-scores.png"/&gt;&lt;/p&gt;
&lt;p&gt;This assessment seems to have a slightly better split of user scores, which is progress I suppose.&lt;/p&gt;
&lt;h2 id="hard-bracket-combinations"&gt;Hard - Bracket Combinations&lt;a class="headerlink" href="#hard-bracket-combinations" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;There is only one hard assessment available publicly. The goal for this assessment is to return the number of combinations you can make with a number 
of pairs of parenthesis. The example provided is:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The possible combinations of 3 pairs of parenthesis, namely: ()()(), are ()()(), ()(()), (())(), ((())), and (()())&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I copied the prompt and boiler plate code into ChatGPT and it returned the following response:&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;BracketCombinations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="kn"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nn"&gt;math&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;factorial&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;factorial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;num&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="n"&gt;factorial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;factorial&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&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;# keep this function call here &lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BracketCombinations&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Experience here says that is odd. Technically speaking, Python will allow you to have that &lt;code&gt;import&lt;/code&gt; statemnet inside a function, but if I saw one of 
my developers do it, I'd mention it during a code review. I was curious though and submitted it as is. &lt;/p&gt;
&lt;p&gt;I also went back and told ChatGPT:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Write the function without importing &lt;code&gt;factorial&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It replied with this function:&lt;/p&gt;
&lt;div class="codehilight code"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;def BracketCombinations(num):
    def fact(n):
        result = 1
        for i in range(1, n + 1):
            result &lt;span class="gs"&gt;*= i&lt;/span&gt;
&lt;span class="gs"&gt;        return result&lt;/span&gt;

&lt;span class="gs"&gt;    return fact(2 *&lt;/span&gt; num) // (fact(num) * fact(num + 1))

&lt;span class="gh"&gt;#&lt;/span&gt; keep this function call here 
print(BracketCombinations(int(input())))
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;I submitted this response too.&lt;/p&gt;
&lt;p&gt;I only have one set of images for this challenge, because they both output &lt;em&gt;exactly&lt;/em&gt; the same responses. Both solutions scored the same. Both solutions 
were better than 35.1% of other users. Both solutions had an &lt;code&gt;O(n)&lt;/code&gt; complexity. &lt;/p&gt;
&lt;p&gt;&lt;img alt="CoderByte - Hard Challenge - Combination Score and Complexity" src="https://andrewwegner.com/images/coderbyte-hard-tests-runtime.png"/&gt;&lt;/p&gt;
&lt;p&gt;Showing the complexity is a nice touch. I will admit that I was expecting the solution that imported &lt;code&gt;factorial&lt;/code&gt; to be rejected because it's using a 
built in function instead of rolling your own. Good for CoderByte for allowing a developer to use the built in libraries as an engineer would do in 
a production environment, instead of adding a false constraint. &lt;/p&gt;
&lt;p&gt;&lt;img alt="CoderByte - Hard Challenge - Combination Time Taken" src="https://andrewwegner.com/images/coderbyte-hard-solution-solve-time.png"/&gt;&lt;/p&gt;
&lt;p&gt;Copy pasting to CoderByte kept this close to a minute. &lt;/p&gt;
&lt;h2 id="final-thoughts"&gt;Final Thoughts&lt;a class="headerlink" href="#final-thoughts" title="Permanent link"&gt;¶&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Another code only assessment tool has been shown to be ineffective. The one thing this one does, that others does show publicly, is the 
time it took to solve a problem. I'm very surprised that a majority of these were solved in under ten minutes. Even so, solving a problem in 
less than a minute will probably raise a flag of some kind for a hiring manager giving this type of assessment. I guarentee that time is visbile 
to them.&lt;/p&gt;
&lt;p&gt;These three assessments show how well ChatGPT can be in being your virtual junior engineer. With the react question, I have no idea if it's 
the more efficient way to solve the problem but it works. With the SQL question, experience identified two small problems and allowed me to 
provide feedback and get a corrected query in seconds. The third question turned in two different ways of solving the problem. Apparently, according
to CoderByte, bother of which perform exactly the same.&lt;/p&gt;
&lt;p&gt;The total time it took me to run these three tests was under five minutes. That's a great tool to have in my pocket.&lt;/p&gt;</content><category term="Technical Solutions"/><category term="technical"/><category term="job search"/><category term="chatgpt"/><category term="ai interviews"/></entry></feed>