It has been interesting couple of moths in Python community. Beta version of Python 3.8 was around since summer and everyone was waiting when the official version is going to be released. At various PyCon conferences people shared their excitement about new changes. The wait finished couple of days ago, on October the 14. Python 3.8 is released and the community can finally have benefits from it.

There are many new features in this release and you can see them all at the official release publication. In a nutshell, there are few big features and a lot of small ones. Apart form that, addition optimization is done, so Python 3.8 should be faster than previous versions. In this article, we will focus on the five features that in our opinion are the most important, the most fun and that they will be used the most.

Assignment Expressions & Walrus Operator

One of the first programming languages that I’ve learned was actually Pascall. I know I am a billion years old, but that is not the point 🙂 In this programming language, variable assignment is done using so-called walrus operator – :=. It is called like that due to the fact that it looks like eyes and tusks of the walrus. Now, this operator is available in Python as well. However, in Python this operator is used for assignment expressions.

Effectively, this means that you can assign and return a value in one statement. Also, this means that you can create variables inside of expressions. For example, you can do something like this:

print(x := 11)
view raw walrus.py hosted with ❤ by GitHub

and get the output:

11

As you can see the assignment expression (walrus operator) allows you to assign value 11 to the variable x and print it at the same time. This has many benefits and can make code more readable. Especially, if we work with loops and if statements. For example, if you want to avoid calling len parameter twice:

if (length := len(message)) < 11:
print(f"Message is too short! ({length} characters)")
view raw len.py hosted with ❤ by GitHub

Or, if you want to handle user input in an easier manner, instead of writing something like this:

chat = list()
while True:
message = input("Message: ")
if single_command == "exit chat":
break
chat.append(message)
view raw long.py hosted with ❤ by GitHub

Now we can utilize walrus operator and write it with fewer lines of code:

chat = list()
while (message := input("Insert something: ")) != "exit chat":
inputs.append(message)
view raw chat.py hosted with ❤ by GitHub

Positional-Only Parameters

This is another syntax feature that will change the way we write our code. What are Positional-Only Parameters? Well, their name say a lot. Basically, value of these function parameters during the function call must be located in the place where they are defined and cannot be used as keyword arguments. By this we mean that if you define function like this:

def function(a /, b):
print(a + b)
view raw posparam1.py hosted with ❤ by GitHub

You can call it like this:

# Option 1
function(11, 9)
# Option 2
function(a = 11, 9)
view raw posparam2.py hosted with ❤ by GitHub

However, you can’t call it like this:

# a is not a keyword argument
function(b = 11, 9)
function(11, a = 9)
view raw posparam3.py hosted with ❤ by GitHub

Note that / character is used to define these parameters. Parameters that came before it are positional arguments. So far, this was only available in built in functions like float(). Another cool thing about positional-only parameters is that you can also have them with default values:

def function (a, b = 11, /):
print(a + b)
function(9)
view raw posparam4.py hosted with ❤ by GitHub

Now, if we want to use these with keyword arguments, it can be done like this:

def function(a, b /, c, *, d):
print(a + b + c + d)
view raw posparam5.py hosted with ❤ by GitHub

Or you can forbid using keyword arguments at all using /:

def function(a, b /):
print(a + b)
# Invalid
function(a = 5, b = 6)
# Valid
function(5, 6)
view raw posparam6.py hosted with ❤ by GitHub

Typed Dictionaries

This feature is truly awesome. The problem it is trying to solve is two-folded. First problem is that on one end, type hits don’t cover nested dictionaries. Type hints were initially introduced in Python 3.5, however they have problems with nested types. Representing an object or structured data using (often nested) dictionaries with string keys is a common pattern. Python 3.8 allows type hints to perform static type checks without actually running your code on nested dictionaries. Something like this:

from typing import TypedDict
class Song(TypedDict):
artist: str
title: str
album: str
year: int
song: Song = {'artist': 'Nikola Nezit', 'title': 'Three Suns Halo', 'album': 'Triad', 'year': 2013}
view raw typeddict.py hosted with ❤ by GitHub

This canonical representation of Song will cause no errors when we run some type checker (for example – mypy) is used. This is due to the fact that the dictionary is correct implementation of the Song type. However if we try to do something like this:

from typing import TypedDict
class Song(TypedDict):
artist: str
title: str
album: str
year: int
song: Song = {'artist': 'Nikola Nezit', 'title': 'Three Suns Halo', 'album': 'Triad', 'year': 2013}
def getAlbum(song: Song):
return song['album']
# Wrong types
getAlbum({'artist': 11, 'title': 'Three Suns Halo', 'album': 'Triad', 'year': '2013'})

We will get errors:

typedicterror.py:11: [1m[31merror:[m Incompatible types (expression has type [m[1m"str"[m, TypedDict item [m[1m"year"[m has type [m[1m"int"[m)[m 

typedicterror.py:11: [1m[31merror:[m Incompatible types (expression has type [m[1m"int"[m, TypedDict item [m[1m"artist"[m has type [m[1m"str"[m)[m 

Literal & Final

At the moment, as a part of Python API there are many functions that return different types depending on the value of some function parameter provided. This is evident in other 3rd party libraries as well. For example, pandas.concat(…) returns either Series or DataFrame depending the axis argument (0 – Series, 1 – DataFrame). Thus far it was impossible to detect type signatures of these functions. Let’s say that you need to implement function that has a parameter whose value should be in some range. For example, if the value of an integer parameter is less than 111, we don’t need to process it:

def nBackAtYa(n: int):
if n < 111:
return n
else:
raise ValueError('n must less than 111')

Laterals are another type check feature that can help us with this:

from typing import Literal
def nBackAtYa(n: Literal[11, 33, 66]):
if n < 111:
return n
else:
raise ValueError('n must less than 111')
view raw laterals.py hosted with ❤ by GitHub

If we run mypy we will get something like this:

 latelars.py:8: [1m[31merror:[m Argument 1 to [m[1m"nBackAtYa"[m has incompatible type [m[1m"Literal[43]"[m; expected [m[1m"Union[Literal[11], Literal[33], Literal[66]]"[m[m [1m[31mFound 1 error in 1 file (checked 1 source file)[m 

Final objects are another improvement when it comes to type hints and type checks. Basically sometimes we want to prevent classes from being overriden or inherited. Now, we can use @final decorator. This way we prevent class from being inherited. Apart form that the Final type is also available and it prevents overrides.

from typing import final
@final
class Song:
artist: str
title: str
album: str
year: int
class Derived(Song): # ERROR: Can't inherit from final class "Song"
studio: str
# ———————
from typing import Final
class Song:
artist: str
title: str
album: str
year: Final = 2019
class 2020Song(Song):
Final = 2020 # ERROR: Can't override a final field
view raw finality.py hosted with ❤ by GitHub

F-String Debugging

Have you noticed that a lot of Python libraries are only being supported from Python version 3.6 onward. Some people think this is solely to the fact that f-strings were introduced in that version. With these strings you can do something like this:

rc = "Rubik's Code"
f"{rc} is awesome!"

And the output will be:

' Rubik's Code is awesome!' 

Apart from that, you can use variables and expressions inside of f-strings’ curly braces. They are resolved during run-time. The cool thing in Python 3.8 is that you can use them for debugging. How so? Well you can do something like this:

rc = "Rubik's Code"
f"{rc=}"
view raw fstring1.py hosted with ❤ by GitHub

And the output will be:

'rc="Rubik\'s Code"'

Conclusion

In this article we had a chance to see just some of the interesting new features that are available in Python 3.8. To see the full list and get detailed information about each of the features, makes sure you check this link or simply go through Python Developers Guide. If you are just starting your Python journey, subscribe to our newsletter and get our Python Basics Guide for free.


Read more posts from the author at Rubik’s Code.