What's new in Python 3.10?
Tags: python, programming, productivity
Published: 2021-06-08 19:35:07

<p><strong>Python 3.10</strong>, the latest in-development version of Python, has been&nbsp;<a href="https://www.python.org/downloads/release/python-3100b2/" rel="nofollow">released in its second beta version</a>. With the beta release, the feature set for Python 3.10 has been locked down, and intrepid Python developers are encouraged to test their code against the latest builds (although not in a production environment, of course).</p>

<p>Here&rsquo;s a rundown of all of the major new features in Python 3.10, with discussion of how they can help your code.</p>

<h1><strong>Structural Pattern Matching</strong></h1>

<p>It is the most anticipated feature of Python 3.10. This feature introduces a <strong>switch/case</strong>-like feature as we have in other programming languages to Python. In addition to the switch-like feature, Python 3.10 Structural Pattern Matching brings some extra features, making it even more powerful than the usual switch conditional statement.<br />
It has been added in the form of a&nbsp;<em><strong>match</strong> statement</em>&nbsp;and&nbsp;<em><strong>case </strong>statements</em>&nbsp;of patterns with associated actions. Patterns consist of sequences, mappings, primitive data types as well as class instances.</p>

<p>The generic syntax of pattern matching is:</p>

<pre>
<code class="language-python">match subject:
    case &lt;pattern_1&gt;:
        &lt;action_1&gt;
    case &lt;pattern_2&gt;:
        &lt;action_2&gt;
    case &lt;pattern_3&gt;:
        &lt;action_3&gt;
    case _:
        &lt;action_wildcard&gt;</code></pre>

<p>A match statement takes an expression and compares its value to successive patterns given as one or more case blocks. Specifically, pattern matching operates by:&nbsp;</p>

<ol>
	<li>
	<p>using data with type and shape (the&nbsp;<code>subject</code>)</p>
	</li>
	<li>
	<p>evaluating the&nbsp;<code>subject</code>&nbsp;in the&nbsp;<code>match</code>&nbsp;statement</p>
	</li>
	<li>
	<p>comparing the subject with each pattern in a&nbsp;<code>case</code>&nbsp;statement from top to bottom until a match is confirmed.</p>
	</li>
	<li>
	<p>executing the action associated with the pattern of the confirmed match</p>
	</li>
	<li>
	<p>If an exact match is not confirmed, the last case, a wildcard&nbsp;<code>_</code>, if provided, will be used as the matching case. If an exact match is not confirmed and a wildcard case does not exist, the entire match block is a no-op.</p>
	</li>
</ol>

<p>Let&rsquo;s look at this example as pattern matching in its simplest form: a value, the subject, being matched to several literals, the patterns. In the example below,&nbsp;<code>status</code>&nbsp;is the subject of the match statement. The patterns are each of the case statements, where literals represent request status codes. The associated action to the case is executed after a match:</p>

<pre>
<code class="language-python">def http_error(status):
    match status:
        case 400:
            return "Bad request"
        case 404:
            return "Not found"
        case 418:
            return "I'm a teapot"
        case _:
            return "Something's wrong with the Internet"</code></pre>

<p>&nbsp;</p>

<p>If the above function is passed a&nbsp;<code>status</code>&nbsp;of 418, &ldquo;I&rsquo;m a teapot&rdquo; is returned. If the above function is passed a&nbsp;<code>status</code>&nbsp;of 500, the case statement with&nbsp;<code>_</code>&nbsp;will match as a wildcard, and &ldquo;Something&rsquo;s wrong with the Internet&rdquo; is returned. Note the last block: the variable name,&nbsp;<code>_</code>, acts as a&nbsp;<em>wildcard</em>&nbsp;and insures the subject will always match. The use of&nbsp;<code>_</code>&nbsp;is optional.</p>

<p>You can combine several literals in a single pattern using&nbsp;<code>|</code>&nbsp;(&ldquo;or&rdquo;):</p>

<pre>
<code class="language-python">case 401 | 403 | 404:
    return "Not allowed"</code></pre>

<p>You can learn more about this feature in official documentation available <a href="https://docs.python.org/3.10/whatsnew/3.10.html#pep-634-structural-pattern-matching">here</a>.<br />
&nbsp;</p>

<h1><strong>Parenthesized context managers</strong></h1>

<p>Using enclosing parentheses for continuation across multiple lines in context managers is now supported. This allows formatting a long collection of context managers in multiple lines in a similar way as it was previously possible with import statements. For instance, all these examples are now valid:</p>

<pre>
<code class="language-python">with (CtxManager() as example):
    ...

with (
    CtxManager1(),
    CtxManager2()
):
    ...

with (CtxManager1() as example,
      CtxManager2()):
    ...

with (CtxManager1(),
      CtxManager2() as example):
    ...

with (
    CtxManager1() as example1,
    CtxManager2() as example2
):
    ...</code></pre>

<p>it is also possible to use a trailing comma at the end of the enclosed group:</p>

<pre>
<code class="language-python">with (
    CtxManager1() as example1,
    CtxManager2() as example2,
    CtxManager3() as example3,
):
    ...</code></pre>

<p>Learn more about this feature <a href="https://docs.python.org/3.10/whatsnew/3.10.html#parenthesized-context-managers">here</a>.<br />
&nbsp;</p>

<h1><strong>Better error messages</strong></h1>

<p>In previous versions of Python, debugging a code is pretty difficult due to poor error messages. Error messages generated by the Parser from previous versions of Python are not specific. This version brings more precise messages with it.</p>

<h3><strong>SyntaxErrors</strong></h3>

<p>When parsing code that contains unclosed parentheses or brackets the interpreter now includes the location of the unclosed bracket of parentheses instead of displaying&nbsp;<em>SyntaxError: unexpected EOF while parsing</em>&nbsp;or pointing to some incorrect location. For instance, consider the following code (notice the unclosed &lsquo;{&lsquo;):</p>

<pre>
<code class="language-python">expected = {9: 1, 18: 2, 19: 2, 27: 3, 28: 3, 29: 3, 36: 4, 37: 4,
            38: 4, 39: 4, 45: 5, 46: 5, 47: 5, 48: 5, 49: 5, 54: 6,
some_other_code = foo()</code></pre>

<p>Previous versions of the interpreter reported confusing places as the location of the syntax error:</p>

<pre>
<code class="language-bash">File "example.py", line 3
    some_other_code = foo()
                    ^
SyntaxError: invalid syntax</code></pre>

<p>but in Python 3.10 a more informative error is emitted:</p>

<pre>
<code class="language-bash">File "example.py", line 1
    expected = {9: 1, 18: 2, 19: 2, 27: 3, 28: 3, 29: 3, 36: 4, 37: 4,
               ^
SyntaxError: '{' was never closed</code></pre>

<p>In a similar way, errors involving unclosed string literals (single and triple quoted) now point to the start of the string instead of reporting EOF/EOL.</p>

<h3><strong>IndentationErrors</strong></h3>

<p>Many&nbsp;<a href="https://docs.python.org/3.10/library/exceptions.html#IndentationError" title="IndentationError"><code>IndentationError</code></a>&nbsp;exceptions now have more context regarding what kind of block was expecting an indentation, including the location of the statement:</p>

<pre>
<code class="language-bash">def foo():
   if lel:
   x = 2
  File "&lt;stdin&gt;", line 3
    x = 2
    ^
IndentationError: expected an indented block after 'if' statement in line 2</code></pre>

<h3><br />
<strong>AttributeErrors</strong></h3>

<p>When printing&nbsp;<a href="https://docs.python.org/3.10/library/exceptions.html#AttributeError" title="AttributeError"><code>AttributeError</code></a>,&nbsp;<code>PyErr_Display()</code>&nbsp;will offer suggestions of similar attribute names in the object that the exception was raised from:</p>

<pre>
<code class="language-bash">collections.namedtoplo
Traceback (most recent call last):
  File "&lt;stdin&gt;", line 1, in &lt;module&gt;
AttributeError: module 'collections' has no attribute 'namedtoplo'. Did you mean: namedtuple?</code></pre>

<h3><br />
<strong>NameErrors</strong></h3>

<p>When printing&nbsp;<a href="https://docs.python.org/3.10/library/exceptions.html#NameError" title="NameError"><code>NameError</code></a>&nbsp;raised by the interpreter,&nbsp;<code>PyErr_Display()</code>&nbsp;will offer suggestions of similar variable names in the function that the exception was raised from:</p>

<pre>
<code class="language-bash">&gt;&gt;&gt; schwarzschild_black_hole = None
&gt;&gt;&gt; schwarschild_black_hole
Traceback (most recent call last):
  File "&lt;stdin&gt;", line 1, in &lt;module&gt;
NameError: name 'schwarschild_black_hole' is not defined. Did you mean: schwarzschild_black_hole?</code></pre>

<p><br />
You can learn more about this feature <a href="https://docs.python.org/3.10/whatsnew/3.10.html#better-error-messages">here</a>.<br />
&nbsp;</p>

<h1><strong>New Type Union Operator</strong></h1>

<p>Most of you probably know that Pythn supports <strong>type hints</strong>. They do not guarantee Type Safety but serve as a useful tool in development.<br />
A new type union operator was introduced which enables the syntax&nbsp;<code>X&nbsp;|&nbsp;Y</code>. This provides a cleaner way of expressing &lsquo;either type X or type Y&rsquo; instead of using&nbsp;<a href="https://docs.python.org/3.10/library/typing.html#typing.Union" title="typing.Union"><code>typing.Union</code></a>, especially in type hints.</p>

<p>In previous versions of Python, to apply a type hint for functions accepting arguments of multiple types,&nbsp;<a href="https://docs.python.org/3.10/library/typing.html#typing.Union" title="typing.Union"><code>typing.Union</code></a>&nbsp;was used:</p>

<pre>
<code class="language-python">def square(number: Union[int, float]) -&gt; Union[int, float]:
    return number ** 2</code></pre>

<p>Type hints can now be written in a more succinct manner:</p>

<pre>
<code class="language-python">def square(number: int | float) -&gt; int | float:
    return number ** 2</code></pre>

<p>This new syntax is also accepted as the second argument to&nbsp;<a href="https://docs.python.org/3.10/library/functions.html#isinstance" title="isinstance"><code>isinstance()</code></a>&nbsp;and&nbsp;<a href="https://docs.python.org/3.10/library/functions.html#issubclass" title="issubclass"><code>issubclass()</code></a>:</p>

<pre>
<code class="language-bash">&gt;&gt;&gt; isinstance(1, int | str)
True</code></pre>

<p>Learn more about this feature <a href="https://docs.python.org/3.10/whatsnew/3.10.html#pep-604-new-type-union-operator">here</a>.<br />
&nbsp;</p>

<h1><strong>Other Language Changes</strong></h1>

<ul>
	<li>The&nbsp;<a href="https://docs.python.org/3.10/library/functions.html#int" title="int"><code>int</code></a>&nbsp;type has a new method&nbsp;<a href="https://docs.python.org/3.10/library/stdtypes.html#int.bit_count" title="int.bit_count"><code>int.bit_count()</code></a>, returning the number of ones in the binary expansion of a given integer, also known as the population count.</li>
	<li>The&nbsp;<a href="https://docs.python.org/3.10/library/functions.html#zip" title="zip"><code>zip()</code></a>&nbsp;function now has an optional&nbsp;<code>strict</code>&nbsp;flag, used to require that all the iterables have an equal length.</li>
	<li>Variables can now be declared as type aliases, to allow forward references, more robust errors involving types, and better distinctions between type declarations in scopes.</li>
	<li>OpenSSL 1.1.1 or newer is now required to build CPython. This modernizes one of CPython&rsquo;s key dependencies.</li>
</ul>

<p>Read the entire changes in the official documentation <a href="https://docs.python.org/3.10/whatsnew/3.10.html">here</a>.</p>