<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dmitrii</title>
    <description>The latest articles on DEV Community by Dmitrii (@danfimov).</description>
    <link>https://dev.to/danfimov</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4133406%2F12338f4e-bf9a-451a-aad9-4d80b9b497e5.jpg</url>
      <title>DEV Community: Dmitrii</title>
      <link>https://dev.to/danfimov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/danfimov"/>
    <language>en</language>
    <item>
      <title>Making Python code twice as fast with type annotations</title>
      <dc:creator>Dmitrii</dc:creator>
      <pubDate>Sat, 19 Sep 2026 21:01:18 +0000</pubDate>
      <link>https://dev.to/danfimov/making-python-code-twice-as-fast-with-type-annotations-fio</link>
      <guid>https://dev.to/danfimov/making-python-code-twice-as-fast-with-type-annotations-fio</guid>
      <description>&lt;p&gt;Rewriting parts of a Python library in Rust with &lt;a href="https://github.com/pyo3/pyo3" rel="noopener noreferrer"&gt;PyO3&lt;/a&gt; is a common way to speed it up these days. If you still want the speedup but don't want to leave the cozy world of Python, there is &lt;a href="https://mypyc.readthedocs.io/en/latest/introduction.html" rel="noopener noreferrer"&gt;mypyc&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;: I'll show how to speed up &lt;a href="https://github.com/python-hyper/h11" rel="noopener noreferrer"&gt;h11&lt;/a&gt; (a small HTTP/1.1 library with 710k dependents on GitHub) by roughly a factor of two by compiling it with mypyc. Along the way I'll walk through the interesting errors I hit while adapting the codebase to mypyc, and how I fixed them.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The basics first
&lt;/h2&gt;

&lt;p&gt;You've probably heard of &lt;a href="https://mypy.readthedocs.io/en/stable/" rel="noopener noreferrer"&gt;mypy&lt;/a&gt; (and hopefully used it), but what is mypyc? Mypyc is an ahead-of-time compiler that turns Python code annotated with type hints into CPython C extensions. In plain words: mypyc takes your Python code and, relying on the type annotations, compiles it into &lt;code&gt;.so&lt;/code&gt; or &lt;code&gt;.pyd&lt;/code&gt; files (depending on the platform). Those files hold C extension code, and you can import the result like any regular module. Under the hood, Python code is no longer interpreted on the fly; what runs is native code, precompiled for your platform, and that's where the speedup comes from.&lt;/p&gt;

&lt;p&gt;Type annotations in the source Python code are needed to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pick more efficient representations (for example, "unboxed" integers). Roughly speaking, instead of a &lt;code&gt;PyObject&lt;/code&gt; for an integer you use a plain &lt;code&gt;int32_t&lt;/code&gt; at the C level;&lt;/li&gt;
&lt;li&gt;do early binding (resolve function calls and attribute access at compile time);&lt;/li&gt;
&lt;li&gt;minimize dynamic checks and namespace lookups. That is, no need to walk the whole chain: look into &lt;code&gt;__dict__&lt;/code&gt;, then into the classes (MRO), then take descriptors, &lt;code&gt;__getattribute__&lt;/code&gt; and &lt;code&gt;__getattr__&lt;/code&gt; into account. Instead, the compiler can remember in advance which struct fields to access.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that wherever regular Python calls into compiled code, mypyc inserts explicit type checks that raise &lt;code&gt;TypeError&lt;/code&gt; if the passed types don't match.&lt;/p&gt;

&lt;h2&gt;
  
  
  Fixing the library's type annotations
&lt;/h2&gt;

&lt;p&gt;To try compiling the codebase with mypyc, we first need to fix all the errors in type annotations that mypy reports. Install mypy, put the config into &lt;code&gt;pyproject.toml&lt;/code&gt;, and we can start:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight toml"&gt;&lt;code&gt;&lt;span class="nn"&gt;[tool.mypy]&lt;/span&gt;
&lt;span class="py"&gt;strict&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;warn_unused_configs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;warn_unused_ignores&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="py"&gt;show_error_codes&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;Let's see how bad things are in h11:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; mypy h11
...
Found 13 errors &lt;span class="k"&gt;in &lt;/span&gt;3 files &lt;span class="o"&gt;(&lt;/span&gt;checked 11 &lt;span class="nb"&gt;source &lt;/span&gt;files&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not that bad: a couple of redundant &lt;code&gt;# type: ignore&lt;/code&gt; comments and a few type mismatches. Keep in mind that the library isn't exactly young and supports old Python versions, all the way back to 3.8. To avoid going overboard with backward compatibility, we'll take the latest mypy version at the time of writing (2.3.1) and fix the annotations as if we only had to support Python 3.10+ (all releases still supported).&lt;/p&gt;

&lt;p&gt;Basically, we'll go through three stages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Satisfy mypy and get the code to the point where it reports no errors in strict mode;&lt;/li&gt;
&lt;li&gt;Make mypyc compile everything successfully;&lt;/li&gt;
&lt;li&gt;Make the compiled code work correctly at runtime (imports and tests pass).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;bytes&lt;/code&gt; vs &lt;code&gt;bytearray&lt;/code&gt; in the call chain
&lt;/h3&gt;

&lt;p&gt;Let's go through the errors mypy returns. The &lt;code&gt;ReceiveBuffer.maybe_extract_lines()&lt;/code&gt; &lt;a href="https://github.com/python-hyper/h11/blob/62c5068c971579d61fa1b55373390e12f25fd856/h11/_receivebuffer.py#L104" rel="noopener noreferrer"&gt;method&lt;/a&gt; and the &lt;code&gt;maybe_extract_at_most()&lt;/code&gt; &lt;a href="https://github.com/python-hyper/h11/blob/62c5068c971579d61fa1b55373390e12f25fd856/h11/_receivebuffer.py#L77" rel="noopener noreferrer"&gt;method&lt;/a&gt; return&lt;br&gt;
&lt;code&gt;bytearray&lt;/code&gt; (this is done to avoid copying the data one more time). Meanwhile &lt;code&gt;_obsolete_line_fold&lt;/code&gt;, &lt;code&gt;_decode_header_lines&lt;/code&gt;, &lt;code&gt;validate()&lt;/code&gt; and the &lt;code&gt;Data()&lt;/code&gt; constructor were annotated as accepting &lt;code&gt;bytes&lt;/code&gt;. Hence the mismatch that mypy reports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;h11/_readers.py:53: error: Incompatible types &lt;span class="k"&gt;in &lt;/span&gt;assignment &lt;span class="o"&gt;(&lt;/span&gt;expression has &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="s2"&gt;"bytearray"&lt;/span&gt;, variable has &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="s2"&gt;"bytes | None"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;  &lt;span class="o"&gt;[&lt;/span&gt;assignment]
h11/_readers.py:84: error: Argument 2 to &lt;span class="s2"&gt;"validate"&lt;/span&gt; has incompatible &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="s2"&gt;"bytearray"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; expected &lt;span class="s2"&gt;"bytes"&lt;/span&gt;  &lt;span class="o"&gt;[&lt;/span&gt;arg-type]
h11/_readers.py:87: error: Argument 1 to &lt;span class="s2"&gt;"_decode_header_lines"&lt;/span&gt; has incompatible &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="s2"&gt;"list[bytearray]"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; expected &lt;span class="s2"&gt;"Iterable[bytes]"&lt;/span&gt;  &lt;span class="o"&gt;[&lt;/span&gt;arg-type]
h11/_readers.py:104: error: Argument 2 to &lt;span class="s2"&gt;"validate"&lt;/span&gt; has incompatible &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="s2"&gt;"bytearray"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; expected &lt;span class="s2"&gt;"bytes"&lt;/span&gt;  &lt;span class="o"&gt;[&lt;/span&gt;arg-type]
h11/_readers.py:114: error: Argument 1 to &lt;span class="s2"&gt;"_decode_header_lines"&lt;/span&gt; has incompatible &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="s2"&gt;"list[bytearray]"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; expected &lt;span class="s2"&gt;"Iterable[bytes]"&lt;/span&gt;  &lt;span class="o"&gt;[&lt;/span&gt;arg-type]
h11/_readers.py:134: error: Argument &lt;span class="s2"&gt;"data"&lt;/span&gt; to &lt;span class="s2"&gt;"Data"&lt;/span&gt; has incompatible &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="s2"&gt;"bytearray"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; expected &lt;span class="s2"&gt;"bytes"&lt;/span&gt;  &lt;span class="o"&gt;[&lt;/span&gt;arg-type]
h11/_readers.py:161: error: Argument 1 to &lt;span class="s2"&gt;"_decode_header_lines"&lt;/span&gt; has incompatible &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="s2"&gt;"list[bytearray]"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; expected &lt;span class="s2"&gt;"Iterable[bytes]"&lt;/span&gt;  &lt;span class="o"&gt;[&lt;/span&gt;arg-type]
h11/_readers.py:182: error: Argument 2 to &lt;span class="s2"&gt;"validate"&lt;/span&gt; has incompatible &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="s2"&gt;"bytearray"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; expected &lt;span class="s2"&gt;"bytes"&lt;/span&gt;  &lt;span class="o"&gt;[&lt;/span&gt;arg-type]
h11/_readers.py:204: error: Argument &lt;span class="s2"&gt;"data"&lt;/span&gt; to &lt;span class="s2"&gt;"Data"&lt;/span&gt; has incompatible &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="s2"&gt;"bytearray"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; expected &lt;span class="s2"&gt;"bytes"&lt;/span&gt;  &lt;span class="o"&gt;[&lt;/span&gt;arg-type]
h11/_readers.py:218: error: Argument &lt;span class="s2"&gt;"data"&lt;/span&gt; to &lt;span class="s2"&gt;"Data"&lt;/span&gt; has incompatible &lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="s2"&gt;"bytearray"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; expected &lt;span class="s2"&gt;"bytes"&lt;/span&gt;  &lt;span class="o"&gt;[&lt;/span&gt;arg-type]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To fix this, it's enough to widen the signatures of &lt;code&gt;_obsolete_line_fold&lt;/code&gt;, &lt;code&gt;_decode_header_lines&lt;/code&gt;, &lt;code&gt;validate()&lt;/code&gt; and the &lt;code&gt;Data()&lt;/code&gt; constructor to &lt;code&gt;bytes | bytearray&lt;/code&gt; instead of plain &lt;code&gt;bytes&lt;/code&gt;. In general, we go through every place where a byte string flows from &lt;code&gt;ReceiveBuffer&lt;/code&gt; straight into an API declared for pure &lt;code&gt;bytes&lt;/code&gt;. For this I introduced a helper type and used it throughout the code like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# _util.py
&lt;/span&gt;&lt;span class="n"&gt;ByteLike&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;bytearray&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ByteLike&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A similar problem affects &lt;code&gt;method&lt;/code&gt;/&lt;code&gt;target&lt;/code&gt;/&lt;code&gt;http_version&lt;/code&gt;/&lt;code&gt;reason&lt;/code&gt; (for example &lt;a href="https://github.com/python-hyper/h11/blob/62c5068c971579d61fa1b55373390e12f25fd856/h11/_events.py#L77" rel="noopener noreferrer"&gt;here&lt;/a&gt;) in the &lt;code&gt;Request&lt;/code&gt;/&lt;code&gt;_ResponseBase&lt;/code&gt; constructors: they're annotated as &lt;code&gt;bytes&lt;/code&gt;, but judging by the tests they can also accept &lt;code&gt;bytearray&lt;/code&gt;. We'll replace those too.&lt;/p&gt;

&lt;p&gt;After these changes and removing the unneeded &lt;code&gt;# type: ignore&lt;/code&gt; comments, mypy is clean. Does that mean mypyc can already build the code? Unfortunately, not yet. Running &lt;code&gt;mypyc h11 --exclude 'h11/tests/'&lt;/code&gt; produces a bunch of errors. Let's figure out what needs fixing and how.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fixing the Sentinel metaclass
&lt;/h3&gt;

&lt;p&gt;In &lt;a href="https://github.com/python-hyper/h11/blob/62c5068c971579d61fa1b55373390e12f25fd856/h11/_util.py#L107" rel="noopener noreferrer"&gt;h11/_util.py&lt;/a&gt; there is this class:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Sentinel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__new__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bases&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwds&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;bases&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sentinel&lt;/span&gt;&lt;span class="p"&gt;,)&lt;/span&gt;
        &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__new__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cls&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bases&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;namespace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__class__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;  &lt;span class="c1"&gt;# so that `type(sentinel) is sentinel` works
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__repr__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;IDLE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Sentinel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;metaclass&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Sentinel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a metaclass used to create unique named constants (sentinel values), such as the protocol states &lt;code&gt;CLIENT&lt;/code&gt;, &lt;code&gt;SERVER&lt;/code&gt;, &lt;code&gt;IDLE&lt;/code&gt;, &lt;code&gt;DONE&lt;/code&gt;, &lt;code&gt;NEED_DATA&lt;/code&gt;, &lt;code&gt;PAUSED&lt;/code&gt; and so on.&lt;/p&gt;

&lt;p&gt;mypyc trips over it because it can't compile custom metaclasses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;h11/_util.py:112: error: Inheriting from most &lt;span class="nb"&gt;builtin &lt;/span&gt;types is unimplemented
note: Potential workaround: @mypy_extensions.mypyc_attr&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;native_class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;False&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;v.__class__ = v&lt;/code&gt; hack, which lets every sentinel pass a check like &lt;code&gt;type(IDLE) is IDLE&lt;/code&gt;, simply can't be expressed in a compiled class. There are two ways around this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Follow the hint from mypyc itself and put the &lt;code&gt;@mypyc_attr(native_class=False)&lt;/code&gt; decorator on the class, then carry on. It tells the compiler "don't try to turn this class into a native C extension, leave it as a regular interpreted Python class". The downsides: we get a runtime dependency on &lt;code&gt;mypy_extensions&lt;/code&gt;, and this part of the code won't get any of the speed benefits of compilation.&lt;/li&gt;
&lt;li&gt;Rewrite &lt;code&gt;Sentinel&lt;/code&gt; as an empty class and inherit from it directly with &lt;code&gt;class IDLE(Sentinel)&lt;/code&gt;, giving up the nice &lt;code&gt;__repr__&lt;/code&gt; and &lt;code&gt;type(sentinel) is sentinel&lt;/code&gt;. I think that's acceptable for the purposes of this article (if I were sending a real PR to the library, I'd have to think about it some more).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We fixed the class and rewrote one test that relied on &lt;code&gt;__repr__&lt;/code&gt;, so it looks like we're done. After this, mypyc will actually build your code, and &lt;code&gt;.so&lt;/code&gt; files will appear in the project. Does that mean everything works without errors? Sadly, not yet...&lt;/p&gt;

&lt;h3&gt;
  
  
  Event(ABC) + hand-written slots on dataclasses
&lt;/h3&gt;

&lt;p&gt;If you try to run the simplest script after compilation, you'll get an error:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h11&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; uv run python &lt;span class="nt"&gt;-m&lt;/span&gt; test_script
Traceback &lt;span class="o"&gt;(&lt;/span&gt;most recent call last&lt;span class="o"&gt;)&lt;/span&gt;:
  File &lt;span class="s2"&gt;"&amp;lt;frozen runpy&amp;gt;"&lt;/span&gt;, line 198, &lt;span class="k"&gt;in &lt;/span&gt;_run_module_as_main
  File &lt;span class="s2"&gt;"&amp;lt;frozen runpy&amp;gt;"&lt;/span&gt;, line 88, &lt;span class="k"&gt;in &lt;/span&gt;_run_code
  File &lt;span class="s2"&gt;"/home/danfimov/Documents/projects/h11-mypyc/test_script.py"&lt;/span&gt;, line 1, &lt;span class="k"&gt;in&lt;/span&gt; &amp;lt;module&amp;gt;
    import h11
  File &lt;span class="s2"&gt;"h11/__init__.py"&lt;/span&gt;, line 9, &lt;span class="k"&gt;in&lt;/span&gt; &amp;lt;module&amp;gt;
    from h11._connection import Connection, NEED_DATA, PAUSED
  File &lt;span class="s2"&gt;"h11/_connection.py"&lt;/span&gt;, line 16, &lt;span class="k"&gt;in&lt;/span&gt; &amp;lt;module&amp;gt;
    from ._events import &lt;span class="o"&gt;(&lt;/span&gt;
  File &lt;span class="s2"&gt;"h11/_events.py"&lt;/span&gt;, line 41, &lt;span class="k"&gt;in&lt;/span&gt; &amp;lt;module&amp;gt;
    class Request&lt;span class="o"&gt;(&lt;/span&gt;Event&lt;span class="o"&gt;)&lt;/span&gt;:
AttributeError: &lt;span class="nb"&gt;type &lt;/span&gt;object &lt;span class="s1"&gt;'Request'&lt;/span&gt; has no attribute &lt;span class="s1"&gt;'__slots__'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The cause is a combination of two things: &lt;code&gt;Event&lt;/code&gt; inherits from &lt;code&gt;abc.ABC&lt;/code&gt;, while &lt;code&gt;Request&lt;/code&gt;/&lt;code&gt;Response&lt;/code&gt;/... are frozen &lt;code&gt;@dataclass(init=False, frozen=True)&lt;/code&gt; classes with a hand-written &lt;code&gt;__slots__ = (...)&lt;/code&gt; in the class body. In regular Python this works perfectly well. But when compiled, mypyc creates "native" classes (C extensions, &lt;code&gt;PyTypeObject&lt;/code&gt;) whose slots are already baked into the layout, and combining that with the ABC metaclass at class creation time raises an &lt;code&gt;AttributeError&lt;/code&gt; during module initialization.&lt;/p&gt;

&lt;p&gt;To fix it, it's enough to detach &lt;code&gt;Event&lt;/code&gt; from &lt;code&gt;ABC&lt;/code&gt; and stop declaring slots by hand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# before
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ABC&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;__slots__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;__slots__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;method&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;headers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;target&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http_version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="c1"&gt;# after
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;pass&lt;/span&gt;

&lt;span class="nd"&gt;@dataclass&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;frozen&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;slots&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After this you can also safely drop the &lt;code&gt;super().__init__()&lt;/code&gt; call from &lt;code&gt;Request&lt;/code&gt;/&lt;code&gt;Response&lt;/code&gt;, because it would break even plain Python code.&lt;/p&gt;

&lt;p&gt;The error then changes to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Traceback &lt;span class="o"&gt;(&lt;/span&gt;most recent call last&lt;span class="o"&gt;)&lt;/span&gt;:
  File &lt;span class="s2"&gt;"&amp;lt;frozen runpy&amp;gt;"&lt;/span&gt;, line 198, &lt;span class="k"&gt;in &lt;/span&gt;_run_module_as_main
  File &lt;span class="s2"&gt;"&amp;lt;frozen runpy&amp;gt;"&lt;/span&gt;, line 88, &lt;span class="k"&gt;in &lt;/span&gt;_run_code
  File &lt;span class="s2"&gt;"/home/danfimov/Documents/projects/h11-mypyc/test_script.py"&lt;/span&gt;, line 1, &lt;span class="k"&gt;in&lt;/span&gt; &amp;lt;module&amp;gt;
    import h11
  File &lt;span class="s2"&gt;"h11/__init__.py"&lt;/span&gt;, line 9, &lt;span class="k"&gt;in&lt;/span&gt; &amp;lt;module&amp;gt;
    from h11._connection import Connection, NEED_DATA, PAUSED
  File &lt;span class="s2"&gt;"h11/_connection.py"&lt;/span&gt;, line 26, &lt;span class="k"&gt;in&lt;/span&gt; &amp;lt;module&amp;gt;
    from ._readers import READERS, ReadersType
  File &lt;span class="s2"&gt;"h11/_readers.py"&lt;/span&gt;, line 25, &lt;span class="k"&gt;in&lt;/span&gt; &amp;lt;module&amp;gt;
    from ._state import &lt;span class="o"&gt;(&lt;/span&gt;
  File &lt;span class="s2"&gt;"h11/_state.py"&lt;/span&gt;, line 115, &lt;span class="k"&gt;in&lt;/span&gt; &amp;lt;module&amp;gt;
    from ._events import &lt;span class="k"&gt;*&lt;/span&gt;
ModuleNotFoundError: No module named &lt;span class="s1"&gt;'_events'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This one is even easier to fix: replace &lt;code&gt;from ._events import *&lt;/code&gt; with an explicit import:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;._events&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ConnectionClosed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;EndOfMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;InformationalResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Response&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;After that, our naive check script works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;uv run python &lt;span class="nt"&gt;-m&lt;/span&gt; test_script
&lt;span class="gp"&gt;&amp;lt;class 'h11._events.Event'&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sadly, a fair share of the tests now fail with &lt;code&gt;TypeError&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FAILED h11/tests/test_against_stdlib_http.py::test_h11_as_client - TypeError: bytes object expected; got bytearray
FAILED h11/tests/test_connection.py::test__body_framing - TypeError: bytes object expected; got None
FAILED h11/tests/test_connection.py::test_Connection_basics_and_content_length - TypeError: bytes object expected; got bytearray
FAILED h11/tests/test_connection.py::test_chunked - TypeError: bytes object expected; got bytearray
FAILED h11/tests/test_connection.py::test_chunk_boundaries - TypeError: bytes object expected; got bytearray
FAILED h11/tests/test_connection.py::test_client_talking_to_http10_server - TypeError: bytes object expected; got bytearray
...
23 failed, 55 passed in 0.30s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Maybe Any isn't so bad?
&lt;/h3&gt;

&lt;p&gt;As I mentioned earlier, mypyc inserts explicit type checks that raise &lt;code&gt;TypeError&lt;/code&gt; when the passed types don't match. That's exactly what's happening in the tests.&lt;/p&gt;

&lt;p&gt;Let's look at one of the tests:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_normalize_data_events&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;normalize_data_events&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="p"&gt;[&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;               &lt;span class="nc"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;bytearray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
                &lt;span class="o"&gt;^^^^^^^^^^^^^^^^^^^^^^^^^^&lt;/span&gt;
                &lt;span class="nc"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[]),&lt;/span&gt;
                &lt;span class="nc"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nc"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;4&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nc"&gt;EndOfMessage&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="nc"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nc"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;6&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="nc"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;7&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nc"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;12&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[]),&lt;/span&gt;
            &lt;span class="nc"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;34&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nc"&gt;EndOfMessage&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="nc"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;567&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;h11&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;tests&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;test_helpers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; 

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   &lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;__setattr__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;E&lt;/span&gt;   &lt;span class="nb"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt; &lt;span class="nb"&gt;object&lt;/span&gt; &lt;span class="n"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;got&lt;/span&gt; &lt;span class="nb"&gt;bytearray&lt;/span&gt;

&lt;span class="n"&gt;h11&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;_events&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;293&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;TypeError&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we can see, it fails simply on passing a plain &lt;code&gt;bytearray&lt;/code&gt; as the &lt;code&gt;data&lt;/code&gt; argument. The problem is that the type of &lt;code&gt;Data.data&lt;/code&gt; is declared as &lt;code&gt;bytes&lt;/code&gt;, which causes a &lt;code&gt;TypeError&lt;/code&gt; in compiled code.&lt;/p&gt;

&lt;p&gt;According to the docs, &lt;code&gt;Data.data&lt;/code&gt; is not only &lt;code&gt;bytes&lt;/code&gt; but also "any object your data-writing code knows how to handle and for which &lt;code&gt;len()&lt;/code&gt; returns the number of bytes" — this is official support for &lt;code&gt;sendfile()&lt;/code&gt;-style placeholders. We could widen it to &lt;code&gt;bytes | bytearray&lt;/code&gt;, but, generally speaking, by design it should be able to accept &lt;code&gt;Any&lt;/code&gt; (one of the tests even checks that). So we change the annotation here and in every place where this field is read (see the &lt;code&gt;send_data&lt;/code&gt; method), compile, and the test passes.&lt;/p&gt;

&lt;p&gt;The same story goes for &lt;code&gt;_ResponseBase.status_code&lt;/code&gt;. The constructor of this class explicitly checks &lt;code&gt;isinstance(status_code, int)&lt;/code&gt; and raises &lt;code&gt;LocalProtocolError("status code must be integer")&lt;/code&gt; otherwise, so the API deliberately provides a friendly error for a wrong type. With the parameter annotated as &lt;code&gt;status_code: int&lt;/code&gt;, mypyc itself raises &lt;code&gt;TypeError&lt;/code&gt; before execution reaches that check: the human-readable message from the library never fires, and one of the tests fails. We'll have to widen this one to &lt;code&gt;Any&lt;/code&gt; as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cleaning up unnecessary None
&lt;/h3&gt;

&lt;p&gt;One of the tests fails like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   reason &lt;span class="o"&gt;=&lt;/span&gt; b&lt;span class="s2"&gt;""&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;matches[&lt;span class="s2"&gt;"reason"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; is None &lt;span class="k"&gt;else &lt;/span&gt;matches[&lt;span class="s2"&gt;"reason"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
E   TypeError: bytes object expected&lt;span class="p"&gt;;&lt;/span&gt; got None
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In ABNF, &lt;code&gt;status_line&lt;/code&gt; allows &lt;code&gt;reason&lt;/code&gt; and &lt;code&gt;http_version&lt;/code&gt; to be absent (some servers send &lt;code&gt;HTTP/1.0 200\r\n&lt;/code&gt; without a reason phrase). In that case &lt;code&gt;match.groupdict()&lt;/code&gt; puts &lt;code&gt;None&lt;/code&gt; into the unmatched named group. The function is declared as &lt;code&gt;def validate(...) -&amp;gt; Dict[str, bytes]&lt;/code&gt;, which is essentially a lie, because the dictionary could contain &lt;code&gt;None&lt;/code&gt;. At runtime mypyc checks that all values of the returned dictionary really are &lt;code&gt;bytes&lt;/code&gt;, and fails right on &lt;code&gt;return match.groupdict()&lt;/code&gt;. We can fix this by dropping the unmatched groups before returning:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;validate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ByteLike&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;malformed data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;format_args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;regex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fullmatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="bp"&gt;...&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;LocalProtocolError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupdict&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and on the calling side, read the optional fields through &lt;code&gt;.get(name, default)&lt;/code&gt; instead of &lt;code&gt;matches[name] is None&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;http_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http_version&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;reason&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reason&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Removing cast
&lt;/h3&gt;

&lt;p&gt;The tests in &lt;code&gt;test_connection.py&lt;/code&gt; already contained &lt;code&gt;type: ignore&lt;/code&gt; comments, which hint that something is off:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nf"&gt;_body_framing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;req&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content-length&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,))&lt;/span&gt;  &lt;span class="c1"&gt;# type: ignore
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;Connection._request_method&lt;/code&gt; is an &lt;code&gt;Optional[bytes]&lt;/code&gt;: until a &lt;code&gt;Request&lt;/code&gt; arrives (for example, when the server replies &lt;code&gt;408 Request Timeout&lt;/code&gt; before it has read anything) there is no method yet. In h11 the calling code hid this behind &lt;code&gt;cast(bytes, self._request_method)&lt;/code&gt;, basically saying "trust me, it won't be &lt;code&gt;None&lt;/code&gt; here". In compiled code &lt;code&gt;cast()&lt;/code&gt; does nothing at runtime (it's purely a hint for mypy), while &lt;code&gt;_body_framing&lt;/code&gt; is declared as taking &lt;code&gt;bytes&lt;/code&gt;, so it fails with &lt;code&gt;TypeError&lt;/code&gt; in a real 408 scenario. The fix is to explicitly accept &lt;code&gt;Optional[bytes]&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_body_framing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request_method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Union&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;]):&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;None&lt;/code&gt; correctly fails both comparisons (&lt;code&gt;== b"HEAD"&lt;/code&gt;, &lt;code&gt;== b"GET"&lt;/code&gt;) inside the function, so nothing else in the code needs to change, just the signature and removing &lt;code&gt;cast()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;After that, all the tests finally pass.&lt;/p&gt;

&lt;h2&gt;
  
  
  Measuring the performance gain
&lt;/h2&gt;

&lt;p&gt;Now it's time to measure how much speedup compilation gives us. For this we can build a small toy benchmark: parsing a GET request with a realistic set of browser headers, including a long cookie, plus sending a 1000-byte response.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;Median, req/sec&lt;/th&gt;
&lt;th&gt;Relative to baseline&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;h11&lt;/code&gt;, before changes&lt;/td&gt;
&lt;td&gt;36 414&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;h11&lt;/code&gt;, after changes, pure Python&lt;/td&gt;
&lt;td&gt;37 409&lt;/td&gt;
&lt;td&gt;+2%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;h11&lt;/code&gt;, after changes and compiled&lt;/td&gt;
&lt;td&gt;58 983&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;+62%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The version with our changes didn't get slower (+2% is within the noise of the benchmark). But compilation gave a decent boost. No, it's not the clickbait 2x, but it's still quite noticeable.&lt;/p&gt;

&lt;p&gt;I think we can wrap up here with an interim conclusion: compilation gives a real gain, but it requires careful typing and an understanding of how mypyc-compiled code behaves. As a proof of concept, I forked the h11 repository and published a Python package so I can play with optimizations even more:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/danfimov/h11" rel="noopener noreferrer"&gt;the fork's code&lt;/a&gt; (you can easily check the diff against the upstream code there)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pypi.org/project/h11-mypyc/" rel="noopener noreferrer"&gt;h11-mypyc package on PyPI&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;In fact, a 2x gain can be reached with additional code optimizations and a careful look at &lt;a href="https://docs.python.org/3.15/whatsnew/3.15.html#whatsnew315-sampling-profiler" rel="noopener noreferrer"&gt;Tachyon&lt;/a&gt;/&lt;a href="https://github.com/bloomberg/memray" rel="noopener noreferrer"&gt;Memray&lt;/a&gt; output. Some of them are already applied in the fork. So if the article gets a good response, I'm planning to write a second part dedicated to exactly that.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Thanks for reading. I hope you found the article useful. I'm happy to discuss any questions in the comments.&lt;/p&gt;

</description>
      <category>python</category>
      <category>mypy</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
