<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Tom Kuson</title><link>https://tjkuson.me/</link><description>Recent content on Tom Kuson</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><managingEditor>mail@tjkuson.me (Tom Kuson)</managingEditor><webMaster>mail@tjkuson.me (Tom Kuson)</webMaster><copyright>© 2025 Tom Kuson. All rights reserved.</copyright><lastBuildDate>Tue, 10 Feb 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://tjkuson.me/index.xml" rel="self" type="application/rss+xml"/><item><title>Don't parameterise typing.Final</title><link>https://tjkuson.me/blog/dont-parameterise-typing-final/</link><pubDate>Tue, 10 Feb 2026 00:00:00 +0000</pubDate><author>mail@tjkuson.me (Tom Kuson)</author><guid>https://tjkuson.me/blog/dont-parameterise-typing-final/</guid><description>&lt;p&gt;Python has a &lt;code&gt;typing.Final&lt;/code&gt; annotation that can be used to mark that a name
cannot be reassigned. Type-checkers will complain if a final name is reassigned.&lt;/p&gt;





&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Final&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;CONST&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Final&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;This cannot be reassigned.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;4&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;CONST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;This is a type error.&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;# Error: cannot assign to final name.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Like other type annotations in Python, &lt;code&gt;typing.Final&lt;/code&gt; can be parameterised to
annotate the underlying type as well.&lt;/p&gt;





&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Final&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;2&lt;/span&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;3&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Final&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt; &lt;span class="c1"&gt;# int (mypy 1.19)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;However, this isn&amp;rsquo;t necessary. The type-checker will infer the type of the name
absent the parametrisation. Moreover, the type inferred by the type checker will
likely be the most precise valid type for its context.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Python has a <code>typing.Final</code> annotation that can be used to mark that a name
cannot be reassigned. Type-checkers will complain if a final name is reassigned.</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Final</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">CONST</span><span class="p">:</span> <span class="n">Final</span> <span class="o">=</span> <span class="s2">&#34;This cannot be reassigned.&#34;</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">CONST</span> <span class="o">=</span> <span class="s2">&#34;This is a type error.&#34;</span>  <span class="c1"># Error: cannot assign to final name.</span></span></span></code></pre></div>
<p>Like other type annotations in Python, <code>typing.Final</code> can be parameterised to
annotate the underlying type as well.</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Final</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">PORT</span><span class="p">:</span> <span class="n">Final</span><span class="p">[</span><span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="mi">8080</span>  <span class="c1"># int (mypy 1.19)</span></span></span></code></pre></div>
<p>However, this isn&rsquo;t necessary. The type-checker will infer the type of the name
absent the parametrisation. Moreover, the type inferred by the type checker will
likely be the most precise valid type for its context.</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Final</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">PORT</span><span class="p">:</span> <span class="n">Final</span> <span class="o">=</span> <span class="mi">8080</span>  <span class="c1"># Literal[8080] (mypy 1.19)</span></span></span></code></pre></div>
<p>This differs from other type annotations users might be familiar with, which are
less precise when unparameterised (such as bare generics).</p>
<p>It&rsquo;s only worth parameterising <code>typing.Final</code> if the type that would be inferred
by the type-checker in its absence is incorrect or otherwise undesirable.</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Final</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">lst</span><span class="p">:</span> <span class="n">Final</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl">
</span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="k">def</span> <span class="nf">fn</span><span class="p">(</span><span class="n">o</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">int</span> <span class="o">|</span> <span class="kc">None</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span> <span class="o">...</span>
</span></span><span class="line"><span class="ln">6</span><span class="cl">
</span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="n">fn</span><span class="p">(</span><span class="n">lst</span><span class="p">)</span>  <span class="c1"># Got list[int], expected list[int | None] (mypy 1.19)</span></span></span></code></pre></div>
<p>One solution to the problem above would be to use <code>collections.abc.Sequence</code> in
the function signature instead which, unlike <code>list</code>, is covariant. However, if
we did not want to do that, another solution would be to annotate <code>lst</code> as
<code>typing.Final[list[int | None]]</code> to prevent the type-checker from inferring the
more precise (yet undesired) type of <code>list[int]</code>. The type of <code>lst</code> would then
satisfy the type required by the function signature.</p>
<p>By leaving <code>typing.Final</code> unparameterised, we avoid an extra burden and let the
type-checker infer the best type.</p>
<h2 id="additional-note">Additional note</h2>
<p>Indicating that a name is final means it should not be reassigned. It can,
however, be mutated. Using immutable types (such as tuples) can thus alter the
resulting type.</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Final</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">
</span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">lst</span><span class="p">:</span> <span class="n">Final</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">]</span>  <span class="c1"># list[int]</span>
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">tup</span><span class="p">:</span> <span class="n">Final</span> <span class="o">=</span> <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span>  <span class="c1"># tuple[Literal[1], Literal[2], Literal[3]]</span></span></span></code></pre></div>
]]></content:encoded></item><item><title>Sentinel values in Python</title><link>https://tjkuson.me/blog/sentinel-values/</link><pubDate>Wed, 29 Oct 2025 00:00:00 +0000</pubDate><author>mail@tjkuson.me (Tom Kuson)</author><guid>https://tjkuson.me/blog/sentinel-values/</guid><description>&lt;p&gt;In Python, we sometimes want to create sentinel values (for example, to act as
a placeholder default value distinct from &lt;code&gt;None&lt;/code&gt;). The simplest way to do this
is to create a new object and perform an identity check.&lt;/p&gt;





&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="ln"&gt;1&lt;/span&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Unset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;Let&amp;rsquo;s say that &lt;code&gt;a&lt;/code&gt; can be an &lt;code&gt;int&lt;/code&gt; or &lt;code&gt;None&lt;/code&gt; or this &lt;code&gt;Unset&lt;/code&gt; sentinel value; how
would we type &lt;code&gt;a&lt;/code&gt;? Using &lt;code&gt;int | None | object&lt;/code&gt; would accept all values due to
the union with &lt;code&gt;object&lt;/code&gt;, which we don&amp;rsquo;t want.&lt;/p&gt;</description><content:encoded><![CDATA[<p>In Python, we sometimes want to create sentinel values (for example, to act as
a placeholder default value distinct from <code>None</code>). The simplest way to do this
is to create a new object and perform an identity check.</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="n">Unset</span> <span class="o">=</span> <span class="nb">object</span><span class="p">()</span></span></span></code></pre></div>
<p>Let&rsquo;s say that <code>a</code> can be an <code>int</code> or <code>None</code> or this <code>Unset</code> sentinel value; how
would we type <code>a</code>? Using <code>int | None | object</code> would accept all values due to
the union with <code>object</code>, which we don&rsquo;t want.</p>
<p>We could try creating a new class just for this sentinel.</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln">1</span><span class="cl"><span class="k">class</span> <span class="nc">UnsetT</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">2</span><span class="cl">    <span class="k">pass</span>
</span></span><span class="line"><span class="ln">3</span><span class="cl">
</span></span><span class="line"><span class="ln">4</span><span class="cl"><span class="n">Unset</span> <span class="o">=</span> <span class="n">UnsetT</span><span class="p">()</span></span></span></code></pre></div>
<p>However, <code>int | None | Unset</code> wouldn&rsquo;t narrow to <code>int | None</code> if we performed an
<code>a is not Unset</code> check. Just because a value is not a specific instance of a
type doesn&rsquo;t mean it cannot be some other instance of that type. We&rsquo;d have to
perform an <code>isinstance</code> check, which is more expensive and not nice to read.</p>
<p>For a type-correct sentinel value, we can use an <code>enum.Enum</code> value (which is
special-cased by type-checkers). By doing this, type-checkers can rule out the
enumeration type by checking against its one and only member. Using the new
<code>type</code> keyword, we can avoid exposing this implementation detail (though we
unfortunately need separate names for the sentinel instance and its type).</p>





<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="ln"> 1</span><span class="cl"><span class="kn">import</span> <span class="nn">enum</span>
</span></span><span class="line"><span class="ln"> 2</span><span class="cl">
</span></span><span class="line"><span class="ln"> 3</span><span class="cl"><span class="k">class</span> <span class="nc">_UnsetT</span><span class="p">(</span><span class="n">enum</span><span class="o">.</span><span class="n">Enum</span><span class="p">):</span>
</span></span><span class="line"><span class="ln"> 4</span><span class="cl">    <span class="n">UNSET</span> <span class="o">=</span> <span class="n">enum</span><span class="o">.</span><span class="n">auto</span><span class="p">()</span>
</span></span><span class="line"><span class="ln"> 5</span><span class="cl">
</span></span><span class="line"><span class="ln"> 6</span><span class="cl"><span class="n">Unset</span> <span class="o">=</span> <span class="n">_UnsetT</span><span class="o">.</span><span class="n">UNSET</span>
</span></span><span class="line"><span class="ln"> 7</span><span class="cl"><span class="nb">type</span> <span class="n">UnsetT</span> <span class="o">=</span> <span class="n">_UnsetT</span>
</span></span><span class="line"><span class="ln"> 8</span><span class="cl">
</span></span><span class="line"><span class="ln"> 9</span><span class="cl"><span class="k">def</span> <span class="nf">test</span><span class="p">(</span><span class="n">a</span><span class="p">:</span> <span class="nb">int</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">|</span> <span class="n">UnsetT</span> <span class="o">=</span> <span class="n">Unset</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">10</span><span class="cl">    <span class="k">if</span> <span class="n">a</span> <span class="ow">is</span> <span class="n">Unset</span><span class="p">:</span>
</span></span><span class="line"><span class="ln">11</span><span class="cl">        <span class="k">return</span>
</span></span><span class="line"><span class="ln">12</span><span class="cl">    <span class="n">reveal_type</span><span class="p">(</span><span class="n">a</span><span class="p">)</span>  <span class="c1"># Revealed type is &#34;builtins.int | None&#34;</span></span></span></code></pre></div>
<p>Until <a href="https://peps.python.org/pep-0661/">PEP 661</a> is accepted, this might be
the best we can do!</p>
]]></content:encoded></item></channel></rss>