What’s in python 3.8?

Holden Rehg
7 min readAug 9, 2019

Update — Mar 1st, 2021

Hello! If you’ve seen my articles pop up in your searches before, I really appreciate the time people have taken to read my articles. At this point, there’s been over 75k views across the 25 or so articles I’ve written here on Medium. That’s amazing to me since writing is a hobby I started doing just to try to get a bit better at writing and communicating.

I wanted to let everyone who lands on these articles that I have migrated everything over to a self hosted blog (for a bunch of reasons that I won’t get into here). So please take a look at https://holdenrehg.com/blog for all of the articles or https://holdenrehg.com/blog/2019-08-09_whats-in-python3.8 to specifically see this article.

Thanks again! — Holden

The programming language python is releasing it’s latest minor version 3.8 soon and is currently in beta. Our platform is built on top of python 3, so we wanted to take a look at what’s coming down the pipe and figured we share details about the major changes that caught our eye.

This is not a random marketing article repeating the headlines from the change logs. This is our team playing around with the features and reporting back what we see with examples.

Installing python 3.8 (on linux)

As of today, the latest version is python 3.8.0b3 and is available on the downloads page here. To install the version locally on linux, you can build from source to keep it separate from your other python versions:

$ ./configure $ make $ make test $ sudo make install

Once it’s installed, you can use the executable created:

$ /usr/local/bin/python3.8

Single Dispatch Methods

First up, the newest version of python is introducing a singledispatchmethod annotation into functools. Previously, you had access to overload functions through the singledispatch annotation, but no ability to for class methods.

Here’s what the original function looks like and what still exists for functions in global scope:

from functools import singledispatch @singledispatch def add(a, b): raise NotImplementedError("The `add` function does not support those types.") @add.register(int) @add.register(int) def _(a, b): return a + b @add.register(str) @add.register(str) def _(a, b): return "{} {}".format(a, b) add(5, 5) # => 10 add("first", "last") # => 'first last'

Here’s what you also, now have access to in 3.8:

from functools import singledispatchmethod class Math: @singledispatchmethod def add(self, a, b): raise NotImplementedError("The `add` method does not support those types") @add.register def _(self, a: int, b: int): return a + b @add.register def _(self, a: str, b: str): return "{} {}".format(a, b) Math().add(5, 5) # => 10 Math().add("first", "last") # => 'first last'

Assignment expressions

Essentially this new features provides a way to assign a variable from within an expression using the new syntax of `variable := expr`. Typically when executing some sort of expression, if you need to reuse the result you need a stand alone assignment statement. Many programmers who are focused on clean code will sacrifice performance for the sake of readability. This provides a solution so you can have both.

Here’s an example where we need to get a list of names, broken into a tuple of first and last, if the person’s name can be split. If you want to do that in a list comprehension today, you might end up calling the split function twice to get a simple line of code:

class Person: def __init__(self, name): self.name = name def split_name(name): return name.split(" ") persons = [Person(name="Bob Dylan"), Person(name="Conan O'brian")][ split_name(person.name) for person in persons if split_name(person.name) is not None ] ------------------------------------------- [['Bob', 'Dylan'], ['Conan', "O'brian"]]

Instead you can assign that variable within the expression, essentially caching it for later use:

[name for person in persons if (name := split_name(person.name)) is not None] ------------------------------------------- [['Bob', 'Dylan'], ['Conan', "O'brian"]]

The PEP also shares some great examples where code can be both simplified and performance increased:

while chunk := file.read(8192): process(chunk) # Reuse a value that's expensive to compute [y := f(x), y**2, y**3] # Handle a matched regex if (match := pattern.search(data)) is not None: # Do something with match

Position-only arguments

Currently in python, you have 2 options for argument. They can be positional or keyword.

def my_function(position_arg_1, position_arg_2, keyword_arg=None): pass

This feature introduces a syntax for saying that a function has positional-only arguments, meaning that they cannot be called by keyword.

def positional_only(a, b, /): return a + bpositional_only(a=5, b=6) ------------------------------------------- Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: positional_only() got some positional-only arguments passed as keyword arguments: 'a, b'positional_only(5, 6) ------------------------------------------- 11

So why do we need this? There’s a couple of problems that it’s trying to solve:

1. Break the disconnect between python modules developed in C

Python modules developed using C already support this feature, so there’s a disconnect with developers writing pure python. It’s often confusing running into one of these functions when you aren’t aware the functionality exists.

ord("a") 97 ord(c="a") ------------------------------------------- TypeError Traceback (most recent call last) TypeError: ord() takes no keyword arguments

2. Uncouple the dependencies between keyword and function

For API developers, sometimes a hard dependency on a keyword argument is not a good thing. All of the sudden, you cannot change an argument in module without introducing a breaking change.

Runtime audit hooks

Python does not make monitoring easy, because of the way that its implemented at a low level (at the operating system level.) So while the program may have access to higher level details, like knowing that an HTTP server was started on port 8000, it does not have access to the system level process id or any context around it.

This functionality introduces the ability to generally integrate with system level details, still without worry about the nature of the underlying system.

This happens via 2 features:

1. An audit hook

import sys def audit(event, args): print(f"something happened {event} {args}") sys.addaudithook(audit) with open("/tmp/test.txt", "w+") as file: file.write("abcd") ------------------------------------------- something happened exec (<code object <module> at 0x7fd014fd72f0, file "<stdin>", line 1>,) something happened open ('/tmp/test.txt', 'w+', 524866) 4 something happened compile (None, '<stdin>')

Adding an audit hook allows you to register an event listener that fires anytime a system level event occurs. These could be errors or just standard processes running like `exec` or `open`. They could potentially be networking events like `socket.gethostbyname`.

Honestly, we weren’t able to come up with a great use case for this functionality. These are described as “allows Python embedders to integrate with operating system support when launching scripts or importing Python code.”

It provides support for loading python code itself for access to the binary:

io.open_code(path : str) -> io.IOBase

Typing

Final qualifier to typing

Here’s something that feels very Java-esque. Python is introducing 2 ways to define something as final.

The purpose of these 2 features are the same:

  • Declaring that a method should not be overridden
  • Declaring that a class should not be subclassed
  • Declaring that a variable or attribute should not be reassigned

This is a common feature in other object oriented languages and can serve many different purposes, but it is always used to restrict modification. Restricting other developers from inheriting and modifying your class, from extending and modifying your method, or from modifying a certain variable. There’s some scenarios where the program will fail or become more confusing if a certain class, method, or variable is altered. There’s also some scenarios where developers want to avoid extension because it gives them more rigid control over the implementation of a class or method. Without the rigid control, a subclass or extended method may easily break when the parent class or method is changed in the future.

from typing import final, Final @final class Unbreakable: """ You can't extend me... """ class Base: @final def cannot_change_me(self): """ You can't extend me... """ BOWIE: Final = "what you can't do is change it"

Literal types

Another typing related feature forcing type checks to be literally some type.

from typing import Literal def add(a:Literal[10], b:Literal[5]): return a + b # will not work... add(5, 5) # this is fine add(10, 5)

You can see from our horrible add function above, that we can restrict the arguments to take a literal value in. This is helpful in scenarios where you expect only certain arguments, and potentially change what you are going to do based on those arguments.

Typed hinting dictionaries

Finally, we have an introduction to type hinted dictionaries. Dictionaries can often feel like Frankenstein-ish data structures when used the wrong way. Type hinting dictionaries, in certain scenarios, can add some order to things if needed.

from typing import TypedDict class Person(TypedDict): first_name: str last_name: str# This is fine person: Person = { "first_name": "Bob", "last_name": "Newhart", }# This will fail the type check for `last_name` person: Person = { "first_name": "Bob", "last_name": 5, }

There’s a lot more that you can do with TypedDict through inheritance or applying additional, stricter type checking so read through the full PEP for details. Generally, it looks like a pretty straight forward new addition to the language, especially for basic use cases.

And there’s more

Make sure to get into the full release and see everything that’s offered. We just pointed out the parts that were most interesting to our team over here, but there’s a handful of other cool features especially if you deal with concurrency, serialization, or writing C modules for python.

Thanks for reading

Appreciate you reading this.

If you’re feeling extra nice today, give us a follow on LinkedIn or Twitter to stay up to date with stories and updates about my business, or try out of the free trial of our business management software.

Holden, Founder of Buster Technologies

Originally published at https://blog.bustererp.com on August 9, 2019.

--

--

Holden Rehg

🧑‍💻 Software Freelancer and Consultant | Works mostly with #odoo and #python web apps | Writes at http://holdenrehg.com/blog