<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>alchemmist — Python</title><link>https://alchemmist.xyz/tags/python/</link><description>Последние записи в блоге alchemmist</description><generator>Hugo 0.163.3</generator><language>en</language><atom:link href="https://alchemmist.xyz/tags/python/index.xml" rel="self" type="application/rss+xml"/><lastBuildDate>Wed, 11 Feb 2026 16:02:00 +0300</lastBuildDate><item><title>Typing in Python</title><link>https://alchemmist.xyz/articles/typing-python/</link><pubDate>Wed, 11 Feb 2026 16:02:00 +0300</pubDate><dc:creator>alchemmist</dc:creator><guid>https://alchemmist.xyz/articles/typing-python/</guid><description>This article covers two key questions. Why use typing in Python, a language that lets you write without it? And how do you write typed Python code correctly? We will quickly go through the essential tools so that after reading, you can consciously start writing typed Python programs. It is not that hard.
# Dynamic vs Static So what is typing? For many Python developers, this can feel new, because most classic languages such as C, Java, Rust, and many others were originally designed as statically typed languages. But what does that mean? Let’s look at a small C example:</description><content:encoded><![CDATA[<p>This article covers two key questions. Why use typing in Python, a
language that lets you write without it? And how do you write typed
Python code correctly? We will quickly go through the essential tools
so that after reading, you can consciously start writing typed Python
programs. It is not that hard.</p>

<h2 id="dynamic-vs-static">
  <a class="link" href="#dynamic-vs-static">
    #
  </a>
  Dynamic vs Static
</h2>

<p>So what is typing? For many Python developers, this can feel new,
because most classic languages such as C, Java, Rust, and many others
were originally designed as statically typed languages. But what does
that mean? Let&rsquo;s look at a small C example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">sum</span><span class="p">(</span><span class="kt">int</span> <span class="n">a</span><span class="p">,</span> <span class="kt">int</span> <span class="n">b</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">a</span> <span class="o">+</span> <span class="n">b</span><span class="p">;</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">printf</span><span class="p">(</span><span class="s">&#34;%d</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">,</span> <span class="nf">sum</span><span class="p">(</span><span class="mi">10</span><span class="p">,</span> <span class="mi">20</span><span class="p">));</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">    <span class="c1">// printf(&#34;%d\n&#34;, sum(&#34;10&#34;, 20));
</span></span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>This code works and prints <code>30</code>. But note that the last line is
commented out. If we uncomment it and compile again, we will get an
error log like this:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-plaintext" data-lang="plaintext"><span class="line"><span class="cl">error: passing argument 1 of ‘sum’ makes integer from pointer without cast
</span></span><span class="line"><span class="cl">   10 |     printf(&#34;%d\n&#34;, sum(&#34;10&#34;, 20));
</span></span><span class="line"><span class="cl">      |                        ^~~~
</span></span><span class="line"><span class="cl">      |                        |
</span></span><span class="line"><span class="cl">      |                        char *
</span></span><span class="line"><span class="cl">note: expected ‘int’ but argument is of type ‘char *’
</span></span></code></pre></div><p>The log says that the function parameter expected <code>int</code> but got
<code>char *</code> (<em>for simplicity, think of it as a string</em>). At first glance,
this may not look surprising for Python developers, because this Python
code would also fail:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">sum</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">a</span> <span class="o">+</span> <span class="n">b</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="nb">sum</span><span class="p">(</span><span class="s2">&#34;10&#34;</span><span class="p">,</span> <span class="mi">20</span><span class="p">))</span> <span class="c1"># TypeError</span>
</span></span></code></pre></div><p>So what is the difference? Let&rsquo;s tweak both examples in C and Python,
and call <code>sum</code> with two strings:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="nb">sum</span><span class="p">(</span><span class="s2">&#34;10&#34;</span><span class="p">,</span> <span class="s2">&#34;20&#34;</span><span class="p">))</span> <span class="c1"># &gt; 1020</span>
</span></span></code></pre></div><p>Here we get no error, because polymorphism works and string addition is
valid. But what happens in C?</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-c" data-lang="c"><span class="line"><span class="cl"><span class="kt">int</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nf">printf</span><span class="p">(</span><span class="s">&#34;%d</span><span class="se">\n</span><span class="s">&#34;</span><span class="p">,</span> <span class="nf">sum</span><span class="p">(</span><span class="s">&#34;10&#34;</span><span class="p">,</span> <span class="s">&#34;20&#34;</span><span class="p">));</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span></code></pre></div><p>You cannot even compile this program. You get the same error log as
before. Notice how we defined <code>sum</code> in C: we explicitly set input
argument types to <code>int</code>. That means arguments of any other type cannot
be passed into this function. This is static typing. Static typing also
requires a type for each variable and prevents changing variable types
after declaration. In other words, the type is fixed.</p>
<p>The second group is dynamically typed languages: Python, Lua,
JavaScript, and others. In those languages, the variable type is not
strictly fixed and can change during execution.</p>

<h2 id="benefits-of-typing">
  <a class="link" href="#benefits-of-typing">
    #
  </a>
  Benefits of Typing
</h2>

<p>Now to the question: why do we need typing if Python already works well
without it? First, speed. At a low level (<em>whatever representation you
use</em>), types are always needed. If we do not write them explicitly, it
only means someone else does that for us (<em>for example, a virtual
machine</em>), which requires resources. That is reflected in language speed
rankings:</p>
<p><img
    src="/images/pl-rating_hu_24d9f448f6699b56.webp" srcset="/images/pl-rating_hu_37edec30c72a3477.webp 360w, /images/pl-rating_hu_24d9f448f6699b56.webp 720w, /images/pl-rating_hu_e59670835513d670.webp 1080w, /images/pl-rating_hu_f117cd474e83bcca.webp 1440w" sizes="900px"
    width="720"
    height="486"
    class="content-img" style="width:900px"
    alt=""
    loading="lazy"
    decoding="async"
    data-zoom-src="/images/pl-rating_hu_f117cd474e83bcca.webp"
  /></p>
<p>In this
<a href="https://github.com/niklas-heer/speed-comparison">rating</a>, Python is at
the bottom. Will typed Python fix that? Unfortunately,
<a href="https://bernsteinbear.com/blog/typed-python/#fnref:simple-annotations">no</a>.
Python was not and still is not a statically typed language. Type
annotations in Python remain optional. You can omit them, and the Python
interpreter does not enforce them at runtime.</p>
<p>This brings us to the second benefit of types: code quality.</p>
<blockquote class="markdown-blockquote">
  <p>&ldquo;The goal of typing in Python is to help development tools find
errors in Python code bases through static analysis, without running
code tests.&rdquo;</p>
<p>Luciano Ramalho, &ldquo;Fluent Python&rdquo;</p>

</blockquote>
<p>Extending this idea, typing goals are:</p>
<ul>
<li>Early error detection, before runtime and before production failures.</li>
<li>&ldquo;Test extraction&rdquo;: correct typing can reduce the number of tests,
which are often harder to write and maintain than types. Tests can
then focus on business logic, not primitive mismatches.</li>
<li>Better readability: <a href="https://peps.python.org/pep-0020/">PEP 20</a>
says &ldquo;Explicit is better than implicit.&rdquo; When reading code, function
signatures are often enough without diving into implementation.</li>
<li>Better IDE workflow with more hints and warnings.</li>
<li>Better architecture quality: types force cleaner abstractions.</li>
</ul>

<h2 id="how-to-write-typed-code">
  <a class="link" href="#how-to-write-typed-code">
    #
  </a>
  How to Write Typed Code
</h2>

<p>Before code, let&rsquo;s separate three important concepts: interface,
abstract class, and protocol. What is the difference?</p>
<ul>
<li><strong>Interface</strong> is a class where all methods are abstract, with no
implementation details.</li>
<li><strong>Abstract class</strong> is a class with both abstract and implemented
methods.</li>
<li><strong>Protocol</strong> is an implicit interface.</li>
</ul>
<p>The first two are straightforward. Let&rsquo;s focus on the last one. In
Python, an interface pattern is usually implemented through inheritance:
a class implementing an interface inherits from that interface class. A
class implementing a protocol does not have to inherit from it and may
not even know about it.</p>

<h3 id="primitives">
  <a class="link" href="#primitives">
    ##
  </a>
  Primitives
</h3>

<p>Let&rsquo;s finally look at how to write typed code. Start with a simple
function that repeats a string <code>n</code> times.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">multi_string</span><span class="p">(</span><span class="n">string</span><span class="p">,</span> <span class="n">n</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">string</span> <span class="o">*</span> <span class="n">n</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">multi_string</span><span class="p">(</span><span class="s2">&#34;cat&#34;</span><span class="p">,</span> <span class="mi">3</span><span class="p">))</span> <span class="c1"># &gt; catcatcat</span>
</span></span></code></pre></div><p>This function takes a string and a number and returns a string built by
concatenating the original string with itself <code>n</code> times. Now type it:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">multi_string</span><span class="p">(</span><span class="n">string</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">n</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">string</span> <span class="o">*</span> <span class="n">n</span>
</span></span></code></pre></div><p>Syntax is simple: input parameter types go after a colon, output type
goes after an arrow. Your IDE can now highlight errors if inputs are
wrong or if returned values are handled incorrectly.</p>
<p><img src="/images/typing-demo-1.gif" class="content-img" style="width:700px" alt="" loading="lazy" decoding="async" /></p>
<p>You can annotate all common primitives this way:
<code>str</code>, <code>int</code>, <code>bytes</code>, <code>float</code>, <code>Decimal</code>, <code>bool</code>.</p>

<h3 id="union-types">
  <a class="link" href="#union-types">
    ##
  </a>
  Union Types
</h3>

<p>In real code there are more complex cases where a function should accept
multiple types, but not arbitrary ones. In those cases use unions:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">normalize</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="nb">bytes</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="nb">bytes</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="n">data</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s2">&#34;utf-8&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">data</span>
</span></span></code></pre></div><p>Here <code>normalize</code> accepts either a string or bytes and always returns a
UTF-8 string. With <code>union</code> (<code>|</code>), you can list multiple types, but you
should use this carefully. If you feel like writing a long union of ten
types, pause and reconsider. We will cover ways to handle such cases
below.</p>
<p>Unions are also valid for return types, but mostly for optional
results. Example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">parse_int</span><span class="p">(</span><span class="n">value</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span> <span class="o">|</span> <span class="kc">None</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">value</span><span class="o">.</span><span class="n">isdigit</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nb">int</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
</span></span></code></pre></div><p>This means the function result is optional. It may return <code>int</code>, or may
not return one if parsing fails. But annotating returns as <code>int | str</code>
or other unrelated combinations is usually bad practice. It becomes
unclear how to process the result. In practice, return either a concrete
type with known methods/attributes, or <code>None</code>. Breaking this rule tends
to complicate code.</p>

<h3 id="collection-specification">
  <a class="link" href="#collection-specification">
    ##
  </a>
  Collection Specification
</h3>

<p>Besides primitives, you can and should annotate collections. Avoid plain
<code>data: list</code>; specify element types too. Use square brackets:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">format_user</span><span class="p">(</span><span class="n">user</span><span class="p">:</span> <span class="nb">tuple</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">int</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">name</span><span class="p">,</span> <span class="n">score</span> <span class="o">=</span> <span class="n">user</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">name</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">score</span><span class="si">}</span><span class="s2"> points&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">average</span><span class="p">(</span><span class="n">values</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">float</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">float</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">values</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nb">sum</span><span class="p">(</span><span class="n">values</span><span class="p">)</span> <span class="o">/</span> <span class="nb">len</span><span class="p">(</span><span class="n">values</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">total_count</span><span class="p">(</span><span class="n">counters</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">int</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nb">sum</span><span class="p">(</span><span class="n">counters</span><span class="o">.</span><span class="n">values</span><span class="p">())</span>
</span></span></code></pre></div><p>Of course, this does not cover all production scenarios. In
<a href="/articles/typing-python/#typeddict">TypedDict</a>, <a href="/articles/typing-python/#namedtuple">NamedTuple</a>, and
<a href="/articles/typing-python/#dataclass">Dataclass</a>, we extend this toolkit.</p>

<h3 id="mapping-and-mutablemapping">
  <a class="link" href="#mapping-and-mutablemapping">
    ##
  </a>
  Mapping and MutableMapping
</h3>

<p><code>Mapping</code> and <code>MutableMapping</code> are abstract base classes for dict-like
structures. <code>Mapping</code> guarantees read-only behavior (keys, values,
iteration), while <code>MutableMapping</code> says the object is mutable.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">collections.abc</span> <span class="kn">import</span> <span class="n">Mapping</span><span class="p">,</span> <span class="n">MutableMapping</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">read_config</span><span class="p">(</span><span class="n">cfg</span><span class="p">:</span> <span class="n">Mapping</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">cfg</span><span class="p">[</span><span class="s2">&#34;DATABASE_URL&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">patch_config</span><span class="p">(</span><span class="n">cfg</span><span class="p">:</span> <span class="n">MutableMapping</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</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="cl">    <span class="n">cfg</span><span class="p">[</span><span class="s2">&#34;DEBUG&#34;</span><span class="p">]</span> <span class="o">=</span> <span class="s2">&#34;1&#34;</span>
</span></span></code></pre></div><p>If a function only reads data, use <code>Mapping</code>. If it mutates data, use
<code>MutableMapping</code>. This is a small but important signal for readers and
type checkers.</p>

<h3 id="namedtuple">
  <a class="link" href="#namedtuple">
    ##
  </a>
  NamedTuple
</h3>

<p>When you need a fixed set of fields while keeping tuple behavior,
<code>NamedTuple</code> is useful. It is immutable and indexable, but also lets you
access fields by name.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">NamedTuple</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">User</span><span class="p">(</span><span class="n">NamedTuple</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="nb">id</span><span class="p">:</span> <span class="nb">int</span>
</span></span><span class="line"><span class="cl">    <span class="n">username</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="cl">    <span class="n">score</span><span class="p">:</span> <span class="nb">int</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">print_user</span><span class="p">(</span><span class="n">user</span><span class="p">:</span> <span class="n">User</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;</span><span class="si">{</span><span class="n">user</span><span class="o">.</span><span class="n">username</span><span class="si">}</span><span class="s2"> (</span><span class="si">{</span><span class="n">user</span><span class="o">.</span><span class="n">id</span><span class="si">}</span><span class="s2">) = </span><span class="si">{</span><span class="n">user</span><span class="o">.</span><span class="n">score</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span></code></pre></div><p><code>NamedTuple</code> is good for compact data structures that should not change
after creation. If you need mutability and richer behavior, prefer
<code>dataclass</code> or a regular class.</p>

<h3 id="typeddict">
  <a class="link" href="#typeddict">
    ##
  </a>
  TypedDict
</h3>

<p>If your structure is a dictionary and you want typed keys and values,
use <code>TypedDict</code>. It describes dictionary shape and works at static
analysis level.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">TypedDict</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">User</span><span class="p">(</span><span class="n">TypedDict</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="nb">id</span><span class="p">:</span> <span class="nb">int</span>
</span></span><span class="line"><span class="cl">    <span class="n">username</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="cl">    <span class="n">email</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">send_email</span><span class="p">(</span><span class="n">user</span><span class="p">:</span> <span class="n">User</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="cl">    <span class="o">...</span>
</span></span></code></pre></div><p>This is especially useful when data comes from JSON or another dynamic
source but you still want a strict key contract. If some keys are
optional, define that explicitly to keep type checks meaningful.</p>

<h3 id="dataclass">
  <a class="link" href="#dataclass">
    ##
  </a>
  Dataclass
</h3>

<p><code>dataclass</code> is a convenient way to define a data container with
initializer, comparisons, and readable <code>repr</code>. This class is mutable by
default and works well for domain objects and DTOs.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">User</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nb">id</span><span class="p">:</span> <span class="nb">int</span>
</span></span><span class="line"><span class="cl">    <span class="n">username</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="cl">    <span class="n">email</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="kc">None</span> <span class="o">=</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">normalize</span><span class="p">(</span><span class="n">user</span><span class="p">:</span> <span class="n">User</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">User</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">user</span><span class="o">.</span><span class="n">username</span> <span class="o">=</span> <span class="n">user</span><span class="o">.</span><span class="n">username</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">user</span>
</span></span></code></pre></div><p><code>dataclass</code> is a good fit when you need mutable structure and clear data
modeling. If an object must be immutable, use
<code>@dataclass(frozen=True)</code>.</p>

<h3 id="enum">
  <a class="link" href="#enum">
    ##
  </a>
  Enum
</h3>

<p><code>Enum</code> helps define a closed set of values. This is useful when a field
has a strict list of allowed options and you do not want random strings
in your code.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">enum</span> <span class="kn">import</span> <span class="n">Enum</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">Status</span><span class="p">(</span><span class="n">Enum</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="n">NEW</span> <span class="o">=</span> <span class="s2">&#34;new&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">DONE</span> <span class="o">=</span> <span class="s2">&#34;done&#34;</span>
</span></span><span class="line"><span class="cl">    <span class="n">FAILED</span> <span class="o">=</span> <span class="s2">&#34;failed&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">is_done</span><span class="p">(</span><span class="n">status</span><span class="p">:</span> <span class="n">Status</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">bool</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">status</span> <span class="ow">is</span> <span class="n">Status</span><span class="o">.</span><span class="n">DONE</span>
</span></span></code></pre></div><p>This approach is practical for statuses, roles, flags, modes, and other
enumerable domain values.</p>

<h3 id="custom-classes">
  <a class="link" href="#custom-classes">
    ##
  </a>
  Custom Classes
</h3>

<p>The type system can specify not only primitives but also your own or
library classes. For example:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">User</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nb">id</span><span class="p">:</span> <span class="nb">int</span>
</span></span><span class="line"><span class="cl">    <span class="n">username</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="cl">    <span class="n">email</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="cl">    <span class="n">friends</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="n">User</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">hand_shake</span><span class="p">(</span><span class="n">user1</span><span class="p">:</span> <span class="n">User</span><span class="p">,</span> <span class="n">user2</span><span class="p">:</span> <span class="n">User</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="cl">    <span class="n">user1</span><span class="o">.</span><span class="n">friends</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">user2</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">user2</span><span class="o">.</span><span class="n">friends</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">user1</span><span class="p">)</span>
</span></span></code></pre></div>
<h3 id="abstract-classes">
  <a class="link" href="#abstract-classes">
    ##
  </a>
  Abstract Classes
</h3>

<p>Python has an excellent module, <code>collections.abc</code>. It already defines a
large set of abstract classes that cover most practical needs. They are
useful when you want to describe behavior, not a concrete
implementation. What is available there?</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">collections</span><span class="o">.</span><span class="n">abc</span><span class="o">.</span><span class="n">ABCMeta</span>
</span></span><span class="line"><span class="cl"><span class="n">collections</span><span class="o">.</span><span class="n">abc</span><span class="o">.</span><span class="n">AsyncGenerator</span>
</span></span><span class="line"><span class="cl"><span class="n">collections</span><span class="o">.</span><span class="n">abc</span><span class="o">.</span><span class="n">AsyncIterable</span>
</span></span><span class="line"><span class="cl"><span class="n">collections</span><span class="o">.</span><span class="n">abc</span><span class="o">.</span><span class="n">AsyncIterator</span>
</span></span><span class="line"><span class="cl"><span class="n">collections</span><span class="o">.</span><span class="n">abc</span><span class="o">.</span><span class="n">Awaitable</span>
</span></span><span class="line"><span class="cl"><span class="n">collections</span><span class="o">.</span><span class="n">abc</span><span class="o">.</span><span class="n">Buffer</span>
</span></span><span class="line"><span class="cl"><span class="n">collections</span><span class="o">.</span><span class="n">abc</span><span class="o">.</span><span class="n">ByteString</span>
</span></span><span class="line"><span class="cl"><span class="n">collections</span><span class="o">.</span><span class="n">abc</span><span class="o">.</span><span class="n">Callable</span>
</span></span><span class="line"><span class="cl"><span class="n">collections</span><span class="o">.</span><span class="n">abc</span><span class="o">.</span><span class="n">Collection</span>
</span></span><span class="line"><span class="cl"><span class="n">collections</span><span class="o">.</span><span class="n">abc</span><span class="o">.</span><span class="n">Container</span>
</span></span><span class="line"><span class="cl"><span class="n">collections</span><span class="o">.</span><span class="n">abc</span><span class="o">.</span><span class="n">Coroutine</span>
</span></span><span class="line"><span class="cl"><span class="n">collections</span><span class="o">.</span><span class="n">abc</span><span class="o">.</span><span class="n">EllipsisType</span>
</span></span><span class="line"><span class="cl"><span class="n">collections</span><span class="o">.</span><span class="n">abc</span><span class="o">.</span><span class="n">FunctionType</span>
</span></span><span class="line"><span class="cl"><span class="n">collections</span><span class="o">.</span><span class="n">abc</span><span class="o">.</span><span class="n">Generator</span>
</span></span><span class="line"><span class="cl"><span class="n">collections</span><span class="o">.</span><span class="n">abc</span><span class="o">.</span><span class="n">GenericAlias</span>
</span></span><span class="line"><span class="cl"><span class="n">collections</span><span class="o">.</span><span class="n">abc</span><span class="o">.</span><span class="n">Hashable</span>
</span></span><span class="line"><span class="cl"><span class="n">collections</span><span class="o">.</span><span class="n">abc</span><span class="o">.</span><span class="n">ItemsView</span>
</span></span><span class="line"><span class="cl"><span class="n">collections</span><span class="o">.</span><span class="n">abc</span><span class="o">.</span><span class="n">Iterable</span>
</span></span><span class="line"><span class="cl"><span class="n">collections</span><span class="o">.</span><span class="n">abc</span><span class="o">.</span><span class="n">Iterator</span>
</span></span><span class="line"><span class="cl"><span class="n">collections</span><span class="o">.</span><span class="n">abc</span><span class="o">.</span><span class="n">KeysView</span>
</span></span><span class="line"><span class="cl"><span class="n">collections</span><span class="o">.</span><span class="n">abc</span><span class="o">.</span><span class="n">Mapping</span>
</span></span><span class="line"><span class="cl"><span class="n">collections</span><span class="o">.</span><span class="n">abc</span><span class="o">.</span><span class="n">MappingView</span>
</span></span><span class="line"><span class="cl"><span class="n">collections</span><span class="o">.</span><span class="n">abc</span><span class="o">.</span><span class="n">MutableMapping</span>
</span></span><span class="line"><span class="cl"><span class="n">collections</span><span class="o">.</span><span class="n">abc</span><span class="o">.</span><span class="n">MutableSequence</span>
</span></span><span class="line"><span class="cl"><span class="n">collections</span><span class="o">.</span><span class="n">abc</span><span class="o">.</span><span class="n">MutableSet</span>
</span></span><span class="line"><span class="cl"><span class="n">collections</span><span class="o">.</span><span class="n">abc</span><span class="o">.</span><span class="n">Reversible</span>
</span></span><span class="line"><span class="cl"><span class="n">collections</span><span class="o">.</span><span class="n">abc</span><span class="o">.</span><span class="n">Sequence</span>
</span></span><span class="line"><span class="cl"><span class="n">collections</span><span class="o">.</span><span class="n">abc</span><span class="o">.</span><span class="n">Set</span>
</span></span><span class="line"><span class="cl"><span class="n">collections</span><span class="o">.</span><span class="n">abc</span><span class="o">.</span><span class="n">Sized</span>
</span></span><span class="line"><span class="cl"><span class="n">collections</span><span class="o">.</span><span class="n">abc</span><span class="o">.</span><span class="n">ValuesView</span>
</span></span></code></pre></div><p>As you can see, there are many abstract classes. Most describe a
property of a collection. Using them in annotations lets you express
wider polymorphic input boundaries for your functions:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">collections.abc</span> <span class="kn">import</span> <span class="n">Iterable</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">total</span><span class="p">(</span><span class="n">values</span><span class="p">:</span> <span class="n">Iterable</span><span class="p">[</span><span class="nb">int</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="nb">sum</span><span class="p">(</span><span class="n">values</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">total</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="cl"><span class="n">total</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="cl"><span class="n">total</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></code></pre></div><p>Sometimes you still see imports from <code>typing</code> like
<code>from typing import Iterable, Sequence</code>. In practice, those are
re-exports from <code>collections.abc</code>. Today it is better to import these
ABCs directly from <code>collections.abc</code>.</p>

<h3 id="sequence-and-iterable">
  <a class="link" href="#sequence-and-iterable">
    ##
  </a>
  Sequence and Iterable
</h3>

<p>These two are often confused. <code>Iterable</code> only guarantees that an object
can be iterated in a loop. No indexing, length, or ordering is
promised. <code>Sequence</code> guarantees iteration plus indexing and length, that
is <code>__getitem__</code> and <code>__len__</code>. This leads to a practical difference in
what operations are safe.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">collections.abc</span> <span class="kn">import</span> <span class="n">Iterable</span><span class="p">,</span> <span class="n">Sequence</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">sum_any</span><span class="p">(</span><span class="n">values</span><span class="p">:</span> <span class="n">Iterable</span><span class="p">[</span><span class="nb">int</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">total</span> <span class="o">=</span> <span class="mi">0</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">values</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="n">total</span> <span class="o">+=</span> <span class="n">v</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">total</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">head</span><span class="p">(</span><span class="n">values</span><span class="p">:</span> <span class="n">Sequence</span><span class="p">[</span><span class="nb">int</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">values</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
</span></span></code></pre></div><p><code>sum_any</code> accepts a list, tuple, or generator. <code>head</code> cannot accept a
generator, because it has no indexing and you cannot do <code>values[0]</code>.
So, if you only need iteration, use <code>Iterable</code>; if you rely on indexing
or length, use <code>Sequence</code>.</p>

<h3 id="callable">
  <a class="link" href="#callable">
    ##
  </a>
  Callable
</h3>

<p><code>Callable</code> is used when a function accepts another function. This is
especially important for callbacks, event handlers, and higher-order
functions.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">collections.abc</span> <span class="kn">import</span> <span class="n">Callable</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">apply</span><span class="p">(</span><span class="n">values</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">int</span><span class="p">],</span> <span class="n">fn</span><span class="p">:</span> <span class="n">Callable</span><span class="p">[[</span><span class="nb">int</span><span class="p">],</span> <span class="nb">int</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">list</span><span class="p">[</span><span class="nb">int</span><span class="p">]:</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="p">[</span><span class="n">fn</span><span class="p">(</span><span class="n">v</span><span class="p">)</span> <span class="k">for</span> <span class="n">v</span> <span class="ow">in</span> <span class="n">values</span><span class="p">]</span>
</span></span></code></pre></div><p>If the signature is unknown in advance, you can use
<code>Callable[..., ReturnType]</code>, but this should be a last resort.</p>

<h3 id="generics">
  <a class="link" href="#generics">
    ##
  </a>
  Generics
</h3>

<p>Generics are parameterized types. In simple terms, these are types that
accept other types. This matters when you want to preserve the relation
between input and output instead of losing it to <code>Any</code>. For example,
<code>first</code> returns the same item type as inside the input collection:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">collections.abc</span> <span class="kn">import</span> <span class="n">Sequence</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">TypeVar</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">T</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;T&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">first</span><span class="p">(</span><span class="n">items</span><span class="p">:</span> <span class="n">Sequence</span><span class="p">[</span><span class="n">T</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">T</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">items</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
</span></span></code></pre></div><p>Without generics, you would write <code>Sequence[Any]</code> and lose output type.
With generics, the analyzer knows that if input is <code>Sequence[str]</code>, the
result is <code>str</code>. This is especially important for collections, factories,
and repositories where one implementation handles multiple types.</p>
<p>It is also useful to know that <code>TypeVar</code> has a <code>bound</code> parameter that
restricts which types can be used in a generic:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">collections.abc</span> <span class="kn">import</span> <span class="n">Hashable</span><span class="p">,</span> <span class="n">Iterable</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">TypeVar</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">HashableT</span> <span class="o">=</span> <span class="n">TypeVar</span><span class="p">(</span><span class="s2">&#34;HashableT&#34;</span><span class="p">,</span> <span class="n">bound</span><span class="o">=</span><span class="n">Hashable</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">mode</span><span class="p">(</span><span class="n">data</span><span class="p">:</span> <span class="n">Iterable</span><span class="p">[</span><span class="n">HashableT</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="n">HashableT</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span></code></pre></div>
<h3 id="literal">
  <a class="link" href="#literal">
    ##
  </a>
  Literal
</h3>

<p><code>Literal</code> lets you fix exact allowed values, not just a base type. This
is useful when a parameter has a closed set of modes, statuses, or keys.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Literal</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">export_report</span><span class="p">(</span><span class="nb">format</span><span class="p">:</span> <span class="n">Literal</span><span class="p">[</span><span class="s2">&#34;csv&#34;</span><span class="p">,</span> <span class="s2">&#34;json&#34;</span><span class="p">])</span> <span class="o">-&gt;</span> <span class="nb">bytes</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="o">...</span>
</span></span></code></pre></div><p>The signature above defines the contract clearly: no third format
exists here. This makes APIs clearer and lets type checkers catch typos
such as <code>&quot;jsno&quot;</code> before runtime.</p>

<h3 id="static-analyzers">
  <a class="link" href="#static-analyzers">
    ##
  </a>
  Static Analyzers
</h3>

<p>Now it is time to discuss what makes typing practical in Python:
static analyzers. They read annotations, compare them with actual code,
and report problems before runtime.</p>
<ul>
<li><code>mypy</code> is the classic static type checker for gradual typing.
Strength: plugin ecosystem and precise per-module strictness tuning.
Weakness: without tuning it can be either too soft or too noisy.</li>
<li><code>pyright</code> is a fast and clear checker.
Strength: good diagnostics and fast feedback.
Weakness: plugin-style extensibility is weaker than in <code>mypy</code>.</li>
<li><code>pyrefly</code> is a new fast Rust analyzer.
Strength: high speed and LSP integration.
Weakness: still young, so some behavior may change.</li>
<li><code>ty</code> is a new Rust tool by Astral (currently beta).
Strength: speed and modern architecture.
Weakness: pre-release stage; feature set is still catching up to
mature checkers.</li>
</ul>
<p>You can install and run them with <code>uv</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">uv tool install mypy
</span></span><span class="line"><span class="cl">uv tool install pyright
</span></span><span class="line"><span class="cl">uv tool install pyrefly
</span></span><span class="line"><span class="cl">uv tool install ty
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">uvx mypy .
</span></span><span class="line"><span class="cl">uvx pyright .
</span></span><span class="line"><span class="cl">uvx pyrefly check
</span></span><span class="line"><span class="cl">uvx ty check
</span></span></code></pre></div><p>Here is a business-oriented example with intentional typing mistakes:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">dataclasses</span> <span class="kn">import</span> <span class="n">dataclass</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">NewType</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">UserId</span> <span class="o">=</span> <span class="n">NewType</span><span class="p">(</span><span class="s2">&#34;UserId&#34;</span><span class="p">,</span> <span class="nb">int</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nd">@dataclass</span>
</span></span><span class="line"><span class="cl"><span class="k">class</span> <span class="nc">User</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="nb">id</span><span class="p">:</span> <span class="n">UserId</span>
</span></span><span class="line"><span class="cl">    <span class="n">email</span><span class="p">:</span> <span class="nb">str</span>
</span></span><span class="line"><span class="cl">    <span class="n">is_active</span><span class="p">:</span> <span class="nb">bool</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">discount</span><span class="p">(</span><span class="n">total</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">percent</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">total</span> <span class="o">-</span> <span class="n">total</span> <span class="o">*</span> <span class="p">(</span><span class="n">percent</span> <span class="o">/</span> <span class="mi">100</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">send_invoice</span><span class="p">(</span><span class="n">user</span><span class="p">:</span> <span class="n">User</span><span class="p">,</span> <span class="n">amount</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">str</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="k">if</span> <span class="ow">not</span> <span class="n">user</span><span class="o">.</span><span class="n">is_active</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">        <span class="k">return</span> <span class="kc">None</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="sa">f</span><span class="s2">&#34;invoice for </span><span class="si">{</span><span class="n">user</span><span class="o">.</span><span class="n">email</span><span class="si">}</span><span class="s2">: </span><span class="si">{</span><span class="n">amount</span><span class="si">}</span><span class="s2">&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">main</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="cl">    <span class="n">user</span> <span class="o">=</span> <span class="n">User</span><span class="p">(</span><span class="nb">id</span><span class="o">=</span><span class="mi">42</span><span class="p">,</span> <span class="n">email</span><span class="o">=</span><span class="mi">123</span><span class="p">,</span> <span class="n">is_active</span><span class="o">=</span><span class="s2">&#34;yes&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">total</span> <span class="o">=</span> <span class="n">discount</span><span class="p">(</span><span class="s2">&#34;1000&#34;</span><span class="p">,</span> <span class="mi">10</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="n">send_invoice</span><span class="p">(</span><span class="n">user</span><span class="p">,</span> <span class="s2">&#34;500&#34;</span><span class="p">)</span>
</span></span></code></pre></div><p>Running <code>pyright</code> on this file will produce messages roughly like:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-plaintext" data-lang="plaintext"><span class="line"><span class="cl">error: Type &#34;float&#34; is not assignable to return type &#34;int&#34;
</span></span><span class="line"><span class="cl">error: Type &#34;None&#34; is not assignable to return type &#34;str&#34;
</span></span><span class="line"><span class="cl">error: &#34;Literal[42]&#34; is not assignable to &#34;UserId&#34;
</span></span><span class="line"><span class="cl">error: &#34;Literal[123]&#34; is not assignable to &#34;str&#34;
</span></span><span class="line"><span class="cl">error: &#34;Literal[&#39;yes&#39;]&#34; is not assignable to &#34;bool&#34;
</span></span><span class="line"><span class="cl">error: &#34;Literal[&#39;1000&#39;]&#34; is not assignable to &#34;int&#34;
</span></span><span class="line"><span class="cl">error: &#34;Literal[&#39;500&#39;]&#34; is not assignable to &#34;int&#34;
</span></span></code></pre></div><p>What matters in this output:</p>
<ul>
<li>Errors in <code>discount</code> and <code>send_invoice</code> show function contract
violations: the signature promises one thing, implementation does
another.</li>
<li>Errors in <code>main</code> show boundary-layer issues (input/DTO) passing
wrong types into domain logic.</li>
<li>The <code>UserId</code> error demonstrates why <code>NewType</code> is useful in business
code: a user identifier cannot be accidentally replaced by a plain
<code>int</code> without an explicit decision.</li>
</ul>

<h3 id="stub-files-pyi">
  <a class="link" href="#stub-files-pyi">
    ##
  </a>
  Stub Files (.pyi)
</h3>

<p>Sometimes you want typing for code you cannot or should not edit.
Examples: generated code, third-party library code, or your own module
where you do not want to mix typing and implementation. That is what
stub files with <code>.pyi</code> extension are for.</p>
<p>A <code>.pyi</code> file contains type signatures only and no implementation.
Static analyzers look for these files near source code or in dedicated
<code>types-*</code> packages. Example:</p>
<p><code>calc.py</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">add</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="k">return</span> <span class="n">a</span> <span class="o">+</span> <span class="n">b</span>
</span></span></code></pre></div><p><code>calc.pyi</code>:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">add</span><span class="p">(</span><span class="n">a</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="nb">int</span><span class="p">:</span> <span class="o">...</span>
</span></span></code></pre></div><p>This way you can keep typing separate from implementation, sometimes
even without access to source code.</p>

<h3 id="typealias">
  <a class="link" href="#typealias">
    ##
  </a>
  TypeAlias
</h3>

<p>When a type gets complex, move it into an alias to keep signatures
readable. This is especially useful for business terms like <code>UserId</code>,
<code>Currency</code>, or <code>Payload</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">TypeAlias</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">UserId</span><span class="p">:</span> <span class="n">TypeAlias</span> <span class="o">=</span> <span class="nb">int</span>
</span></span><span class="line"><span class="cl"><span class="n">Payload</span><span class="p">:</span> <span class="n">TypeAlias</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span> <span class="o">|</span> <span class="nb">int</span> <span class="o">|</span> <span class="nb">float</span><span class="p">]</span>
</span></span></code></pre></div><p>Then you can write:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">send</span><span class="p">(</span><span class="n">user_id</span><span class="p">:</span> <span class="n">UserId</span><span class="p">,</span> <span class="n">payload</span><span class="p">:</span> <span class="n">Payload</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="cl">    <span class="o">...</span>
</span></span></code></pre></div><p>In Python 3.12+, you can use the <code>type</code> statement instead:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="nb">type</span> <span class="n">UserId</span> <span class="o">=</span> <span class="nb">int</span>
</span></span><span class="line"><span class="cl"><span class="nb">type</span> <span class="n">Payload</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">str</span> <span class="o">|</span> <span class="nb">int</span> <span class="o">|</span> <span class="nb">float</span><span class="p">]</span>
</span></span></code></pre></div><p>This is the same alias, just shorter and easier to read.</p>

<h3 id="typealias-vs-newtype">
  <a class="link" href="#typealias-vs-newtype">
    ##
  </a>
  TypeAlias vs NewType
</h3>

<p><code>TypeAlias</code> is just a type synonym, it does not create a new type.
<code>NewType</code> creates a distinct type at static-checking level, even though
at runtime it is just a wrapper function. This is useful when you have
logically different values of the same base type, for example <code>UserId</code>
and <code>OrderId</code>.</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">NewType</span><span class="p">,</span> <span class="n">TypeAlias</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">UserId</span><span class="p">:</span> <span class="n">TypeAlias</span> <span class="o">=</span> <span class="nb">int</span>
</span></span><span class="line"><span class="cl"><span class="n">OrderId</span> <span class="o">=</span> <span class="n">NewType</span><span class="p">(</span><span class="s2">&#34;OrderId&#34;</span><span class="p">,</span> <span class="nb">int</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_user</span><span class="p">(</span><span class="n">user_id</span><span class="p">:</span> <span class="n">UserId</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="cl">    <span class="o">...</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">get_order</span><span class="p">(</span><span class="n">order_id</span><span class="p">:</span> <span class="n">OrderId</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="cl">    <span class="o">...</span>
</span></span></code></pre></div><p><code>UserId</code> and <code>int</code> are treated as the same type. <code>OrderId</code> is not
compatible with <code>int</code> without explicit conversion.</p>

<h2 id="how-to-use-typing-correctly">
  <a class="link" href="#how-to-use-typing-correctly">
    #
  </a>
  How to Use Typing Correctly
</h2>

<p>Set the right expectation first. Like tests, the purpose of types is to
<strong>fail</strong> when the code is wrong. If they never fail, they are not doing
useful work. The less forgiving your typing setup is, the better it
protects your code. Strict typing (<em>like strict tests</em>) catches bugs,
but only if checks can actually fail.</p>
<p>Writing truly correct typed code is hard. It is a separate skill that
grows the same way architecture and testing skills grow. So start small
and increase strictness gradually.</p>

<h3 id="returning-none">
  <a class="link" href="#returning-none">
    ##
  </a>
  Returning <code>None</code>
</h3>

<p>Also consider the case where a function returns nothing:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="k">def</span> <span class="nf">print_weather</span><span class="p">(</span><span class="n">weather</span><span class="p">:</span> <span class="n">Weather</span><span class="p">):</span>
</span></span><span class="line"><span class="cl">    <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;Weather:&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">    <span class="k">for</span> <span class="n">date</span><span class="p">,</span> <span class="n">data</span> <span class="ow">in</span> <span class="n">weather</span><span class="o">.</span><span class="n">by_days</span><span class="p">()</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="n">date</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\t</span><span class="si">{</span><span class="n">data</span><span class="o">.</span><span class="n">temperature</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\t</span><span class="si">{</span><span class="n">data</span><span class="o">.</span><span class="n">humidity</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;</span><span class="se">\t</span><span class="si">{</span><span class="n">data</span><span class="o">.</span><span class="n">wind_speed</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">        <span class="nb">print</span><span class="p">(</span><span class="s2">&#34;========&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">tmp</span> <span class="o">=</span> <span class="n">print_weather</span><span class="p">(</span><span class="n">Weather</span><span class="p">())</span> <span class="o">+</span> <span class="mi">1</span>
</span></span></code></pre></div><p>In Python, if a function has no <code>return</code>, it returns <code>None</code>. Static
analyzers know this too. For example, <code>pyright</code> on this intentionally
broken code will show:</p>
<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-plaintext" data-lang="plaintext"><span class="line"><span class="cl">error:
</span></span><span class="line"><span class="cl">    Operator &#34;+&#34; not supported for types &#34;None&#34; and &#34;Literal[1]&#34;
</span></span></code></pre></div><p>This shows that <code>pyright</code> understands the return type as <code>None</code>. Does
that mean you can skip return annotations? No. First,
<a href="https://peps.python.org/pep-0020/">PEP 20</a> says explicit is better
than implicit. Second, consider developer experience (DX) in Python. Everyone
knows typing is optional, so missing return annotation is ambiguous.
When you see a function signature without return type, you cannot tell
whether the author skipped typing or the function really returns <code>None</code>.
You resolve that only by reading implementation details, which slows
code navigation.</p>

<h3 id="function-input-vs-output">
  <a class="link" href="#function-input-vs-output">
    ##
  </a>
  Function Input vs Output
</h3>

<p>Continuing the return-type topic: avoid using abstract classes from
<code>collections.abc</code>, or custom abstract classes, for function return
types. They are better suited for input types where you want broad
polymorphism. Return values should be concrete so it is clear how to
use the result.</p>





<blockquote class="markdown-alert markdown-alert--important">
  <div class="markdown-alert__title">
    
      <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v9.5A1.75 1.75 0 0 1 14.25 13H8.06l-2.573 2.573A1.458 1.458 0 0 1 3 14.543V13H1.75A1.75 1.75 0 0 1 0 11.25Zm1.75-.25a.25.25 0 0 0-.25.25v9.5c0 .138.112.25.25.25h2a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h6.5a.25.25 0 0 0 .25-.25v-9.5a.25.25 0 0 0-.25-.25Zm7 2.25v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 9a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z"/></svg>
    

    
      Important
    
  </div>

  <p>A function should be more explicit about what concrete type it
returns than what it accepts.</p>
</blockquote>
<br>
<hr>
<br>
<p>If you want to go deeper, here are useful resources to configure your
tools and understand Python typing in detail:</p>
<ul>
<li><a href="https://fastapi.tiangolo.com/python-types/#type-hints-with-metadata-annotations">FastAPI type hints guide (helpful for web
developers)</a></li>
<li><a href="https://realpython.com/python-type-checking/">RealPython, Type Checking
Guide</a></li>
<li><a href="https://python-type-challenges.zeabur.app/">Exercises</a></li>
</ul>
]]></content:encoded><category>python</category><category>eosp</category></item></channel></rss>