Subscribe and receive free guide - Ultimate Data Visualization Guide with Python

* indicates required

Today, on the 4th October 2021 a new version of Python – Python 3.10 has been released. In this article, we explore some features that we have found interesting and that we think will make our code cleaner. A usual with each Python release, there are several big topics and a couple of smaller features and in this article, we cover five which we think are going to make the biggest impact.

Ultimate Guide to Machine Learning with Python

This bundle of e-books is specially crafted for beginners.
Everything from Python basics to the deployment of Machine Learning algorithms to production in one place.
Become a Machine Learning Superhero 
TODAY!

Before we go into features and their details, we first learn how we can install the latest version. In this article we cover:

0. Installing Python 3.10 

  1. Structural Pattern Matching
  2. Better Error Messages
  3. Parenthesized Context Managers
  4. New Type Union Operator
  5. Precise Line Numbers for Debugging

0. Installing Python 3.10 

Linux

To install the latest betta version of Python 3.10 on the Linux machine, first, get the installation zip using wget:

wget https://www.python.org/ftp/python/3.10.0/Python-3.10.tgz

Unpack it:

tar xzvf Python-3.10.tgz
cd Python-3.10
./configure --prefix=$HOME/python-3.10

And run the make file:

make
make install
$HOME/python-3.10/bin/python3.10

Windows and Mac

To install the latest Python 3.10 on Windows or Mac machine, first, get the installation file from this link and run the installation file. Then it is just a matter of going through the setup:

Python 3.10 Installation window for Windows

1. Structural Pattern Matching

This feature is definitely going to have the biggest impact. If you are familiar with a switch-case statement from any other language, this one is that, but on steroids. Wait, there was no switch-case mechanism in Python until now? Why is it called pattern matching? What is the difference? Let’s see.

1.1 Current Problem

Yes, Python was not supporting switch-case statements, or similar concepts until now. Weird, I know. There were several attempts to include this concept in the language, however, they were always rejected.

Nobody has been able to suggest an implementation that can be integrated properly with Python’s syntax and the overall spirit of the language. This means that if you wanted to check multiple values, you need to do something like this:

def http_error(status):
  if status == 400:
    return "Bad request"
  elif status == 404:
    return "Not found"
  elif status == 418:
    return "Rock is not dead"

Or something like this:

def http_error(status):
  return_value = {
    400: "Bad request",
    404: "Not found",
    418: "Rock is not dead"	}

  return return_value[status]
Recommendation Systems

To be honest, I was ok with it. In general, I am not a big fan of the switch-case statement in other languages as well. It usually means that architecture could be done better or that you could use concepts like polymorphism and dictionaries.

That being said, there are certain situations in which a switch-case statement is useful. Apart from that, pattern matching is a bit more than just a simple switch-case statement. Its real power can be found in type and shape comparison.

1.2 Python 3.10 Feature

The easiest way to use this Python feature is to compare a variable with several values. There are two new keywords match and case. The example from above then can be written down like this:

def http_error(status):
	match status:
		case 400:
			return "Bad request"
		case 404:
			return "Not found"
		case 418:
			return "Rock is not dead"
		case _:
			return "Well, this is embaresing"

So, you know, a regular switch-case statement. The last one case _ is the default value – wildcard, so if the status has a value that is not 400, 404, or 418, it will return “Well, this is embarrassing” as a result. This is optional, so if you don’t include it and some out-of-the-scope value appears, the behavior is a no-op. This wildcard is very powerful and can help us with more complex pattern matching, as we will see in a bit.

You can also chain values if you want to do the same thing for a certain case. For example:

def ide_full_name(short_name):
  match short_name:
    case "PyC":
      return "PyCharm"
    case "VSC":
      return "Visual Studio Code"
    case "VS2017" | "VS2019" | "VS2022":
      return "Visual Studio"
    case _:
      return "Not Supported"

Ok, those are the simple examples, now let’s check out why this Python 3.10 feature is really cool.

1.2.1 Structural Pattern Matching and Collections

As you imagine you can use a match-case combo to compare the complete collection. For example, if you want to check if the list is having a certain value, you can do this:

match x:
    case [1, 2] : 
      print('First option')
    case [1, 2, 3] : 
      print('Second option')

The really interesting possibility comes with the wildcard. You can do something like this:

match x:
    case [1, 2] : 
      print('First option')
    case [1, 2, 3] : 
      print('Second option')
    case [1, 2, _]:
      print('Third option')
    case _:    
      print('Default option')

Note that in the third option, we used wildcard just for the third element of the list. That is wild…card! cool

I can imagine many possibilities for this, especially when working with a large amount of data and having edge cases. The other interesting thing is that you can do partial matches with collections. Like for example for tuples:

# point is an (x, y) tuple
  match point:
    case (0, 0):
      print("Origin")
    case (0, y):
      print(f"Y={y}")
    case (x, 0):
      print(f"X={x}")
    case (x, y):
      print(f"X={x}, Y={y}")
    case _:
      raise ValueError("Not a point")

1.2.2 Structural Pattern Matching and Classes

With classes, you can do even more interesting things. For example, something like this:

  class Rectangle:
    hight: int
    width: int

  def check_rectangle(rec):    
    match rec:
      case Rectangle(hight=0, width=0):
        print("The rectangle is a point")
      case Rectangle(hight=0, width = width):
        print("The rectangle is a vertical line.")
      case Rectangle(hight=hight, width = 0):
        print("The rectangle is a horizontal line.")
      case Rectangle():
        print("Just a normal rectangle, folks.")
      case _:
        print("Not a rectangle")

Here we have a Rectangle class and a check_rectangle() function. Within this function, you can find pattern matching. Here you can see that you can actually compare one object with a certain pattern.

These patterns are resembling constructors. This can be extended even further with so-called guards. Essentially, guards are just an if clause within a pattern. So, if we extend the previous example:

  class Rectangle:
    hight: int
    width: int

  def check_rectangle(rec):    
    match rec:
      case Rectangle(hight=0, width=0):
        print("The rectangle is a point")
      case Rectangle(hight=0, width = width):
        print("The rectangle is a vertical line.")
      case Rectangle(hight=hight, width = 0):
        print("The rectangle is a horizontal line.")
      case Rectangle(hight=hight, width=width) if hight == width :
          print("The rectangle is a square")
      case Rectangle():
        print("Just a normal rectangle, folks.")
      case _:
        print("Not a rectangle")

We can use it like this:

  rec = Rectangle()
  rec.hight = 0
  rec.width = 0            
  check_rectangle(rec)

  rec = Rectangle()
  rec.hight = 11
  rec.width = 0
  check_rectangle(rec)

  rec = Rectangle()
  rec.hight = 0
  rec.width = 11
  check_rectangle(rec)

  rec = Rectangle()
  rec.hight = 33
  rec.width = 33
  check_rectangle(rec)
The rectangle is a point
The rectangle is a horizontal line.
The rectangle is a vertical line.
The rectangle is a square

2. Better Error Messages

In general, Python error messages can be misleading and confusing. That is why this feature is here to help us. This feels like an important topic that is going to make our day-to-day job a lot easier and I am really looking forward to it.

Programming

2.1 Current Problem

There are several types of errors where the current Python incarnation doesn’t provide enough information. For example, observe SytaxError for the following code:

data = [2, 9, 11, 33, 56, 93, 111
processed_data = process_data(data)
File "<ipython-input-5-e9aa07ca09db>", line 2
processed_data = process_data(data)
^

As you can see, the square bracket is missing at the end of the first line. However, SyntaxError reports a problem in the second line. A similar situation can happen if we forget to close string quotes:

word = "BirdIsThe
processed_data = process_data(data)
File "<ipython-input-6-44253c1ee7d8>", line 1
word = "BirdIsThe
            ^
SyntaxError: EOL while scanning string literal
SyntaxError: invalid syntax

In this case, it can be even more misleading because EOL or EOF is reported. Finally, the last problem that we can face is if we try to do something like this.

some_function(x, y for y in range(11))
File "<ipython-input-8-8b1dcc7e2f02>", line 1
some_function(x, y for y in range(11))
                 ^
SyntaxError: Generator expression must be parenthesized

Now, this example might be a bit simple, because we have only two parameters in the function. However, SyntaxError is just where the problem is detected.

2.2 Python 3.10 Feature

There are several areas where we will get better messages from Python 3.10. Let’s start with the problems displayed in the chapter above. If you forget to close the bracket like in the example above you will get this:

data = [2, 9, 11, 33, 56, 93, 111,
   ^
SyntaxError: '[' was never closed

Or if you forget to close the string:

word = "BirdIsThe
	   ^
SyntaxError: unterminated string literal (detected at line 1)

Or if you messed up your generator expression:

some_function(x, y for y in range(11))
		 ^^^^^^^^^^^^^^^^^^^^
SyntaxError: Generator expression must be parenthesized

All these messages are more specific and concrete. However, that is not all. Python 3.10 improved other messages as well. If you forget to put two dots after a statement, now you will know that more explicitly:

if (2 > 9)
if (2 > 9)
		  ^
SyntaxError: expected ':'

Or if you used = insted of == in the mentioned statement:

if (2 = 9):
	pass
if (2 = 9)
        ^
SyntaxError: cannot assign to literal here. Maybe you meant '==' instead of '='?

Also, if you forget comma in collection, Python 3.10 has got your back:

list = [2, 9, 11, 33 56, 93]
File "<stdin>", line 1
list = [2, 9, 11, 33 56, 93]

Talking about collections, if you forgot to assign value to a dictionary key, Python 3.10 will let you know:

dict = { 'a' : 2, 'b' : 9, 'c' : , 'd' : 33 }
File "<stdin>", line 1
dict = { 'a' : 2, 'b' : 9, 'c' : , 'd' : 33 }
                                   ^
SyntaxError: expression expected after dictionary key and ':'

There are other improved messages as well, however, I found these the most useful, especially if you are a beginner.

3. Parenthesized Context Managers

The previous version of Python – Python 3.9, brought some interesting changes. Namely LL(1)-based parser of CPython was replaced with a new PEG-based parser. This opened up many possibilities and we can expect many features that will utilize this change. Anyhow, one of the first in the line of such features is parenthesized context managers.

Computer

3.1 Current Problem

If you try to use multiple with statements across multiple lines with the current version of Python, you can not use parens to break them up. For example, if you try something like this:

with (open("a_really_long_foo") as foo,
      open("a_really_long_bar") as bar):
    pass
Traceback (most recent call last):
  File "<input>", line 1, in <module>
  File "demo.py", line 19
	with (open("a_really_long_foo") as foo,
									^
SyntaxError: invalid syntax

3.2 Python 3.10 Feature

Python 3.10 is able to handle this now. You can use any of the following options for this:

# Variation 0
with (CtxManager() as example):
	pass

# Variation 1
with (
	CtxManager1(),
	CtxManager2()
):
	pass

# Variation 2
with (CtxManager1() as example,
	  CtxManager2()):
	pass

# Variation 3
with (CtxManager1(),
	  CtxManager2() as example):
	pass

# Variation 4
with (
	CtxManager1() as example1,
	CtxManager2() as example2
):
	pass

# Variation 5
with (
CtxManager1() as example1,
CtxManager2() as example2,
CtxManager3() as example3,
):
	pass

4. New Type Union Operator

This is a new feature that will simplify the code. Every Python release comes with a set of type hints features. From our perspective, this one is the most important of such features.

Computer

4.1 Current Problem

With the current Python version, if you want to use type hints for a function that has a parameter that can receive values of different types, you had to use the Union type. Something like this:

def some_funcion(flexible_parameter: Union[int, string]) -> Union[int, string]:
	return flexible_parameter	

4.2 Python 3.10 Feature

Python 3.10 introduces a new union operand – |. What this operand says is that certain parameters can be either Type 1 either Type 2. Now, the previous function can be written like this:

def some_funcion(flexible_parameter: int | string) -> int | string:
	return flexible_parameter

5. Precise Line Numbers for Debugging

Have you noticed that sometimes the debugger doesn’t show the real number of the line where the code has a mistake? It does work well most of the time, but it was not always the most reliable tool.

Partial

5.1 Current Problem

If you use sys.settrace and associated tools you may notice that tracing doesn’t work 100% of the time. Sometimes the line is not correct. Reliability here is not as you would expect from such a mature programming language. The problem in its essence is that the f_lineno attribute of frame objects that are part of event objects should always contain the expected line number

5.2 Python 3.10 Feature

The new version of Python brings more precise and reliable line numbers for debugging, profiling, and coverage tools. Python 3.10 guarantees that tracing events, with the associated and correct line numbers, are generated for all lines of code executed and only for lines of code that are executed. 

In order to do this, Python 3.10 doesn’t rely on the current form of  co_lnotab attribute of the events. This version of Python uses new metho co_lines(), which returns an iterator over bytecode offsets and source code lines. The change is done in a way that the f_lineno attribute of frame objects will always contain the expected line number.

Conclusion

In this article, we had a chance to get familiar with 5 new features that the new version of Python will bring. To be honest I can’t wait to try them all out. Some of them seem like a major improvement and it seems that it will affect how we organize and write Python projects.

Thank you for reading!

Ultimate Guide to Machine Learning with Python

This bundle of e-books is specially crafted for beginners.
Everything from Python basics to the deployment of Machine Learning algorithms to production in one place.
Become a Machine Learning Superhero 
TODAY!

Nikola M. Zivkovic

Nikola M. Zivkovic

Nikola is the author of books: Ultimate Guide to Machine Learning and Deep Learning for Programmers. He loves knowledge sharing, and he is an experienced speaker. You can find him speaking at meetups, conferences, and as a guest lecturer at the University of Novi Sad.

Discover more from Rubix Code

Subscribe now to keep reading and get access to the full archive.

Continue reading