Skip to content

Python 3 Highlights

Bob Kline edited this page Dec 24, 2021 · 1 revision

The following is a list, in no particular order, of features which caught my eye while browsing through the "What new in Python 3.x" sections of the documentation shortly after the release of Python 3.10. Some are things I've been using for a while (some quite heavily), others are new to Python (or just to me).

Language Enhancements

Features added to the language syntax.

Annotations for function arguments, return values, and variables

The interpreter ignores these. They're made available to third-party tools (for example, linters) to make it easier to find gaps between what the programmer thinks she's done and what's actually in the code.

def foo(a: str, b: int) -> str: return a * b
def square(number: int | float) -> int | float: return number ** 2
primes: list[int] = []

Type aliases

Vector = list[float]
def scale(scalar: float, vector: Vector) -> Vector:
    return [scalar * num for num in vector]

Structural pattern matching (match ... case)

This is a big deal: Python now has switch statements, except the keyword used is actually match, not switch. Note that there is an implied break after the code for each case, avoiding the fall-through bugs you get in C.

# GitHub hasn't caught up on the keyword syntax highlighting here yet, but it will some day.
match status:
    case 400:
	print("Bad request")
    case 418:
	print("I'm a teapot")

Keyword-only arguments

In the following example, c cannot be passed as a positional argument. So at some point in the future, if lots more keyword arguments are added, it's possible to replace c=None with **opts, pulling out the values from the opts dictionary in the body of the function or method, without breaking any existing user code.

def foo(a, b, *, c=None): ...

Positional-only arguments

In the following example, c and d can either be passed as positional or as keyword arguments, but a and b can only be passed as positional arguments.

def bar(a, b, /, c, d): ...

So bar(1, 2, 3, 4) is ok, as is bar(1, 2, d=4, c=3). But bar(a=1, b=2, c=3, d=4) will raise an exception. This makes it possible to change the names of parameters in an API without the risk of breaking older code.

Extended iterable unpacking

(a, *rest, b) = range(5) # rest is now [1, 2, 3]

Dictionary comprehensions

{k: v for k, v in stuff}

Set literals

{1, 2}

Formatted string literals (f-strings)

The f-string = support for self-documenting expressions and debugging was added in Python 3.8.

f"{1234567.89:,.2f}" # -> "1,234,567.89"
f"{num_docs=:,d} {elapsed=!s}" # -> "num_docs=14,703 elapsed=0:04:15.614567"

Underscores in numeric literals

vewy_big_number = 1_000_000_000_000 + 0x_FF_FF_FF_FF

Assignment expressions

while (block := f.read(256)) != "": process(block)

Union operators for dict

d, e = {"spam": 1, "eggs": 2}, {"eggs": 3, "cheese": 4}
d | e # -> {"spam": 1, "eggs": 3, "cheese": 4} (d and e are unchanged)
d |= e # d is modified in place

Library Enhancements

New modules, improvements to existing modules, and a new global function.

functools decorators lru_cache and cached_property

These might be my favorite new feature, as I make heavy use of cached properties, and until now I've had to implement the caching part by hand in each property. It's nice to know I'm not the only fan.

from functools import cached_property, lru_cache

@cached_property
def cursor(self):
    return self.session.conn.cursor()

@lru_cache
def lookup_term_name(cdr_id: int) -> str:
    # expensive code to find and normalize the preferred term name ...
    return name

venv now in the standard library

python -m venv cdr

Support for lzma compression in the new lzma module and in tarfile

with tarfile.open("libraries.tar.xz", "r:xz") as tar:
    ...

New pathlib module for managing filesystem paths as objects

from pathlib import Path

for path in Path("~/src").expanduser().glob("**/*.py"):
    with path.open() as fp:
        for line in fp:
            if "import" in line:
                print(f"{path}\t{line.rstrip()}")

New secrets module for generating secure random values

vewy_secwet = secrets.token_bytes(32)

Global breakpoint() function drops you into the debugger at the call site

do_preparatory_stuff()
breakpoint()
start_tricky_part()

String methods to remove prefixes and suffixes

"NCI|definition text ...".removeprefix("NCI|") # -> "definition text ..."

The HTTP status code 418 IM_A_TEAPOT was added to http.HTTPStatus

See https://datatracker.ietf.org/doc/html/rfc2324.html. How have we managed to get by before this bug got fixed?

>>> from http import HTTPStatus
>>> HTTPStatus.IM_A_TEAPOT.value
418
>>> HTTPStatus.IM_A_TEAPOT.description
'Server refuses to brew coffee because it is a teapot.'
>>> HTTPStatus.IM_A_TEAPOT.phrase
"I'm a Teapot"