What's in python 3.8?

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

[PEP 572]

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

[PEP 570]

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 + b
positional_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

[PEP 578]

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`.

2. A verified open hook

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

[PEP 591]

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

  • An @final decorator
  • An Final type annotation

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

[PEP 586]

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

[PEP 589]

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 the free trial of our business management software.

Holden, Founder of Buster Technologies

Show Comments

Get the latest posts delivered right to your inbox.