Sharpen your tusks: it's time for Python 3.8

Nick Anderegg
Developer Relations Engineer
18 Dec 2019

The latest version of Python is now available on Platform.sh! In addition to the deluge of bug fixes and improvements you’d expect with every release, Python 3.8 is packed with new features designed to make your code simpler and the development process easier.

To get your existing applications up and running with Python 3.8 right now, just swap out “3.7” with “3.8” in your .platform.app.yaml file:

    type: "python:3.8"

Commit your changes, push them to a new branch in your repository, and in a matter of moments you’ll be able to test out how your application runs on Python 3.8 in an isolated development environment. Once you’re sure that your application is ready for the version bump, merge it into master, and your upgraded application will be ready for the world to see.

What’s new in Python 3.8?

Sure, it’s nice that you can now use a continue statement within a finally clause, but let’s get right to the feature we know you want to hear about: the walrus operator.

Assignment expressions

The walrus operator :=, formally known as the “assignment expression,” lets you assign a value to a variable at the same time you’re using the variable. This way, you can avoid double function and object calls. The simplest use case is when you need to test a value in an if statement, but also need to refer back to the value within the if block:

items = [10, 20, 30]
if (n := len(items)) <= 3:
    print(f"Not enough values! List only has {n} items, must have at least 4!")

This approach is an alternative to either assigning n = len(items) prior to the if statement or calling len(items) a second time in the print statement. Assignment expressions can also be used in while loops where you need to test a condition and also use the tested value inside the loop:

# Process one hex character at a time, stop when a null character is read
while (val := f.read(1)) != chr(0x0):
    process_input(val)

A word of caution: assignment expressions should be reserved for cases where they’ll simplify code and reduce redundant calls. If using this operator will make your code harder to read, stick with assigning the variable in a separate statement. You can check out PEP 527 for the full specification.

Self-documenting f-strings

Keeping with the theme of making code more readable, Python 3.8 introduces the = operator within f-strings to cause them to self-document. Take this example of wanting to print a selection of variable values:

print(f'idx={idx}, value={val}')

By adding = to the end of an expression within an f-string, the expression itself—as well as the resulting value—will both be printed:

idx = 20
val = {'status': 'operational'}
print(f'{idx=}, {val["status"]=}')

results in the output idx=20, val["status"]='operational'. Now, you don’t have to type out every expression twice!

Positional-only parameters

Two new operators have been introduced to function definition syntax: / and *. The / operator declares that all parameters defined before it in a function definition must be specified positionally, while the * operator declares that all parameters defined after it must be specified as keyword arguments. Any parameters between these two operators can be passed as positional or keyword arguments. Take this function definition, for example:

def f(a, b=None, /, c=None, d=None, *, e, f=None):
    print(a, b, c, d, e, f)

These function calls are valid:

>>> f(1, 2, 3, d=4, e=5, f=6) # c and d can be positional or keyword args
1 2 3 4 5 6
>>> f(1, e=5) # b, c, d, and f are optional arguments
1 None None None 5 None
>>> f(1, 2, 3, e=5) # c can be positional and d can be optional
1 2 3 None 5 None

However, none of these calls are:

>>> f(1, 2, 3, 4, 5)
TypeError: f() takes from 1 to 4 positional arguments but 5 were given
>>> f(1, b=2)
TypeError: f() got some positional-only arguments passed as keyword arguments: 'b'
>>> f(1, 2)
TypeError: f() missing 1 required keyword-only argument: 'e'

One of the biggest benefits of requiring that a function parameter be passed as a positional argument is that you can safely change the names of parameters in the future without breaking any code that calls that function. If a function is defined as def f(a, b=None, /), it can be safely redefined as def f(x, y=None, /) in the future. This is because any call to this function that passes a or b as keyword arguments will raise a TypeError.

Many new features and language improvements

Python 3.8 is packed with so many changes that it’s hard to cover them all in the span of one blog post! If you’re ready to get started with this update, check out the full changelog in the Python docs.