All important features and changes in Python 3.10

If you want to try all the features of the great latest version of Python, you need to install the alpha or beta version. However, given that these versions are not stable, we don’t want to overwrite the default python installation. We will install the Python 3.10 alpha next to the current interpreter.


This can be done by running these commands:

wget https://www.python.org/ftp/python/3.10.0/Python-3.10.0a6.tgz
tar xzvf Python-3.10.0a6.tgz
cd Python-3.10.0a6
./configure --prefix=$HOME/python-3.10.0a6
make
make install
$HOME/python-3.10.0a6/bin/python3.10

After running the code above, you will see a greeting from the IDLE development environment:

Python 3.10.0a6 (default, Mar 27 2021, 11:50:33) [GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

Now, with Python installed, we can take a look at all the new features and changes.

Type checking improvements

If you use type checking, you will be happy to hear that Python 3.10 includes many improvements to type checking, among them type union operator, whose syntax is now cleaner.

# Function that accepts either `int` or `float`
# Old:
def func(value: Union[int, float]) -> Union[int, float]:
    return value

# New:
def func(value: int | float) -> int | float:
    return value

Also, this simple improvement is not limited to type annotations, it can be used with the isinstance () and issubclass () functions:

isinstance("hello", int | str)
# True

Type alias syntax changes

In more early versions of Python added type aliases to create custom class synonyms. In Python 3.9 and earlier, aliases were written like this:

FileName = str

def parse(file: FileName) -> None:
    ...

Here FileName is an alias for the base Python string type. However, starting with Python 3.10, the syntax for defining type aliases will change:

FileName: TypeAlias = str

def parse(file: FileName) -> None:
    ...

This simple change makes it easier for both the programmer and the type checking tool to distinguish a variable assignment from an alias. The new syntax is backward compatible, so you don’t need to update old aliased code.

In addition to these 2 changes, there is another improvement to the typing module – in improvement proposal number 612 it is called Parameter Specification Variables. However, this is not what you find in the bulk of Python code, as this functionality is used to pass a type parameter from one callable type to another callable type, for example in decorators. If you have a place to apply this functionality, watch her PEP

bit_count ()

Starting in Python 3.10, int.bit_count () can be called to count the number of bits in the binary representation of an integer. The function is known as Population Count (popcount):

value = 42
print(bin(value))
# '0b101010'
print(value.bit_count())
# 3

This is certainly good, but let’s be realistic: the implementation of this function is not that difficult, in fact it is just one line:

def bit_count(value):
    return bin(value).count("1")

At the same time, popcount () is another convenient function, it may come in handy at some point; All sorts of useful little functions is one of the reasons Python is so popular: at first glance, everything is available out of the box.

The distutils module is deprecated

In the new version, functions are not only added, but also removed or deprecated. This concerns the distutils package, which was deprecated in 3.10 and will be removed in 3.12. It was replaced for a while by the setuptools and packaging packages, so if you work with either of these packages, you should be fine. That being said, you should probably check to see if distutils is used in your code and start preparing to get rid of this module soon.

Context manager syntax

Context managers are great for opening and closing files, working with database connections, and more, and Python 3.10 makes them a little more convenient. The change allows you to specify several context managers in brackets, which is convenient if you want to create several managers in one with statement:

with (
    open("somefile.txt") as some_file,
    open("otherfile.txt") as other_file,
):
    ...

from contextlib import redirect_stdout

with (open("somefile.txt", "w") as some_file,
      redirect_stdout(some_file)):
    ...

In the code above, you can see that we can even refer to a variable created by one context manager (… as some_file) in the next manager!

These are just two of the many new formats in Python 3.10. The improved syntax is pretty flexible, so I won’t bother myself and show you all the options; I’m pretty sure the new Python will handle whatever you feed it.

Performance improvements

As with all recent Python releases, Python 3.10 will bring performance improvements. The first is optimizing the constructors str (), bytes () and bytearray (), which should be about 30% faster (snippet adapted from example in the Python bug tracker):

~ $ ./python3.10 -m pyperf timeit -q --compare-to=python "str()"
Mean +- std dev: [python] 81.9 ns +- 4.5 ns -> [python3.10] 60.0 ns +- 1.9 ns: 1.36x faster (-27%)
~ $ ./python3.10 -m pyperf timeit -q --compare-to=python "bytes()"
Mean +- std dev: [python] 85.1 ns +- 2.2 ns -> [python3.10] 60.2 ns +- 2.3 ns: 1.41x faster (-29%)
~ $ ./python3.10 -m pyperf timeit -q --compare-to=python "bytearray()"
Mean +- std dev: [python] 93.5 ns +- 2.1 ns -> [python3.10] 73.1 ns +- 1.8 ns: 1.28x faster (-22%)

Another more noticeable optimization (if you use type annotations) is that the function parameters and their annotations are no longer calculated at runtime, but at compile time. Now a function with annotation parameters is created about twice as fast.

Besides, there are several more optimizations in different parts of the core of the language. You can find details about them in these Python bug tracker entries: bpo-41718, bpo-42927 and bpo-43452

Pattern matching

One big feature that you’ve certainly heard of is structured pattern matching, which adds a well-known case statement from other languages. We know how to deal with case, but look at the variation in Python ^ this is not just a switch / case, but also several powerful features that we should explore.

Simple pattern matching consists of the keyword match, followed by an expression, and its result is checked against the patterns specified in successive case statements:

def func(day):
    match day:
        case "Monday":
            return "Here we go again..."
        case "Friday":
            return "Happy Friday!"
        case "Saturday" | "Sunday":  # Multiple literals can be combined with `|`
            return "Yay, weekend!"
        case _:
            return "Just another day..."

In this simple example, we used the variable day as an expression, which is then compared to specific strings in the case. In addition to strings, you may also notice the case with the _ stroke, which is the equivalent of the default keyword in other languages. Although this operator can be omitted, a no-op can occur in this case, which essentially means that None will be returned.

Another point to note in the code above is the | operator, which allows you to combine multiple literals | (another version of it is or).

As I mentioned, the new pattern matching does not end with the basic syntax; on the contrary, it introduces additional capabilities, such as complex pattern matching:

def func(person):  # person = (name, age, gender)
    match person:
        case (name, _, "male"):
            print(f"{name} is man.")
        case (name, _, "female"):
            print(f"{name} is woman.")
        case (name, age, gender):
            print(f"{name} is {age} old.")
        
func(("John", 25, "male"))
# John is man.

In the snippet above, we used a tuple as a match expression. However, we are not limited to tuples: any iterable type will work. You can also see above that the wildcard _ can be used inside complex templates and not only by itself, as in the previous examples. Simple tuples or lists are not always the best approach, so if you prefer classes, you can rewrite the code like this:

from dataclasses import dataclass

@dataclass
class Person:
    name: str
    age: int
    gender: str
    
def func(person):  # person is instance of `Person` class
    match person:
        # This is not a constructor
        case Person(name, age, gender) if age < 18:  # guard for extra filtering
            print(f"{name} is a child.")
        case Person(name=name, age=_, gender="male"):  # Wildcard ("throwaway" variable) can be used
            print(f"{name} is man.")
        case Person(name=name, age=_, gender="female"):
            print(f"{name} is woman.")
        case Person(name, age, gender):  # Positional arguments work
            print(f"{name} is {age} years old.")

func(Person("Lucy", 30, "female"))
# Lucy is woman.
func(Person("Ben", 15, "male"))
# Ben is a child.

Here you can see that class attributes can be mapped to constructor-style templates. Using this approach, individual attributes also go into variables (as in the tuples shown earlier), which can then be manipulated in the appropriate case statement.

Above we can see other features of pattern matching: firstly, the expression in the case is the guard, which is also the condition in the if. This is useful when matching by value is not enough and you need additional checks. Look at the rest of the case statement: you can see that both (name-name) keywords and positional arguments work with syntax similar to constructor syntax; the same is true for the mask _ (or discarded variable).

Pattern matching also allows you to work with nested patterns. Nested templates can use any iterable type: both a constructed object and several such objects that can be iterated over:

match users:
    case [Person(...)]:
        print("One user provided...")
    case [Person(...), Person(...) as second]:  # `as var` can be used to capture subpattern
        print(f"There's another user: {second}")
    case [Person(...), Person(...), *rest]:  # `*var` can be used as unpacking
        print(...)

In such complex patterns, it can be useful to write the subpattern to a variable for further processing. This can be done using the as keyword as shown above in the second case.

Finally, the * operator can be used to “unpacking” variables in the template, this also works with the mask _ in the template * _. If you want to see more examples and a complete tutorial, follow the link to PEP 636

Conclusion

There are many interesting new features in Python 3.10, but this release – alpha (coming soon beta) is still far from being fully tested and ready for a production environment. So it is definitely not worth starting to use it.

It’s probably best to wait for the full release in October and maybe check the page from time to time. What’s new in Python 3.10to see the changes that may appear in the last minutes.

With that said, if you want to upgrade, it might be a good idea to test the first release. beta (which will be in June) to see if your codebase is compatible with all the changes, depressions in the new version, including deprecation and removal of modules and functions.

find outhow to upgrade in other specialties or master them from scratch:

Other professions and courses

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *