Python π

Python π (3.14) beta 1 is out, which means the features are locked in. The big feature this time around are template strings, lots more color (including syntax highlighting in the REPL!), remote debugging, deferred evaluation of annotations, and the usual error message and performance improvements.


Posts in the Python Series2→3 3.7 3.8 3.9 3.10 3.11 3.12 3.13 3.14

Template strings

Possibly the most noticeable addition to Python π is template strings. These are basically identical to f-strings, except they don’t render into a string automatically. So while f"hello {world}" becomes a string, t"hello {world}" is a new type, string.templatelib.Template. You can write functions that take this new type and process it. Here’s an simple example that implements f-strings (but skips handling conversion and format specifiers for clarity):

from string.templatelib import Template, Interpolation


def to_string(template: Template) -> str:
    return "".join(
        item.value if isinstance(item, Interpolation) else item for item in template
    )

world = "world"
assert f"hello {world}" == to_string(t"hello {world}")

Since this is a new type, you can check for in it APIs. It is not lazy, though you can manually build in laziness by evaluating callables, for example. You have access to the original value, the expression string that gave that value, along with the conversion and format_spec options. There are some proposed additions to standard library based on it, but those are deferred to 3.15.

Forcing a Template can be useful. If your API takes only template strings, you can make sure substitutions are sanitized. Unfortunately, that’s a backward incompatible change on an existing API. I expect initial usage will simply allow both, with template strings getting automatic sanitation.

REPL

Color all the things!

The new REPL added color in 3.13, and now we are getting it in many more places. Syntax highlighting is now supported in the REPL. The unittest, (new) json, and calendar command lines now sport colors. And argparse supports color too, with a new color parameter, enabled on the stdlib modules too! Argparse also gets a new suggest_on_error parameter.

Remote debugging

You can now connect pdb from one process to another one, using the process ID with -p PID. This is enabled by a new sys.remote_exec() function, which lets you execute code on an running interpreter in a different process. Other debuggers and profilers can take advantage of this too. Various ways to disable this have also been added; you can even build Python with this disabled.

There’s also a new addition to the python -m asyncio module; new commands ps PID and ptree PID, which allow you to inspect the asyncio state of a running Python process.

More autocompletion

Modules now autocomplete with <tab> (though not attributes inside modules).

Error messages

Many improvements have been made to error messages. These are:

  • “Did you mean” for Python keyword typos
  • Argument unpacking length hints
  • elif following else dedicated message
  • Highlighting for statements in the wrong place
  • Better incorrectly closed string message
  • Incompatible string prefix explanation
  • Better messages for incompatible as targets
  • Better JSON serialization errors with added notes

Faster CPython

There are some speedups from the Faster CPython team:

  • A new opt-in tail call interpreter (when built with LLVM 19+), 3-5% faster, up to 30% faster
  • Official CPython builds now have JIT enabled as a runtime opt-in

There’s now a new InterpreterPoolExecutor, which is an easy way to use subinterpreters.

Start up time for a handful of modules has been improved, like subprocess, tomllib, and asyncio.

Using pdb from the CLI or with breakpoint() now uses the much faster sys.monitoring backend. The popular coverage library can now do branch measurements with sys.monitoring; applying this to the packaging repo cut the test time from 55 seconds to 35 seconds.

Static Typing

The big update here is PEP 649/[PEP 749][], deferred evaluation of type annotations. This brings most of the features we used from __future__ import annotations with less runtime impact, including (most of the) speed, new constructs, and forward references.

This comes with a new library if you need to use the annotations at runtime, annotationlib. This lib has methods to get annotations as strings, values, or forward references. This change should be mostly transparent, and tools like typing.get_origin and typing.get_args continue to work correctly.

The only other change is the unification of types.UnionType and types.Union, which means that the old-style Union[A, B] and the new-style A | B unions are identical. Union is now valid in isinstance.

Smaller features:

  • memoryview is now generic type

Typing development is still active, there are several in-progress PEPs related to typing, but they were deferred to Python 3.15.

Compression

A new namespace, compression, was added, and all the existing compression libraries are now available inside it. So import tarfile can now be written from compression import tarfile. Similarly with lzma, bz2, gzip, and zlib. Don’t worry, the old names are still around.

There’s now a new compression library, as well: zstd, with a similar API to lzma and bz2.

Language

You no longer have to use parentheses around exceptions in the except statement. This was a holdover from Python 2, where leaving them off did something completely different.

try:
    f()
except Err1, Err2:
    pass

Other features

Several new methods made it to pathlib.Path, for copying and moving files and directory trees. There’s also a new .info attribute with path information, filled when using iterdir().

Other features include:

  • map() now supports strict=True, like zip()
  • super() is now pickleable and copyable
  • A few small updates to regex, such as \z added and \B matches an empty string
  • python -c now dedents code passed to it
  • ast.compare() compares two ASTs
  • ast nodes now have more descriptive reprs (finally!)
  • You can now terminate/kill workers in ProcessPoolExecutor
  • You can specify a max buffer size for Executor.map
  • fnmatch.filterfalse() for excluding matches
  • functools.Placeholder to hold a place for positional arguments
  • inspect.ispackage() added
  • io.Reader and io.Writer protocols added
  • python -m json added (with color!)
  • os.reload_environ() reloads the environment
  • More assert methods for unittest.
  • New arguments to better handle file: URls in urllib.

Removals and deprecations

The ability to call control flow altering statements inside a finally block is now a SyntaxWarning. This was considered confusing for some reason (though I believe this was clear with a good a mental model of how exceptions and finally work). Regardless, it’s now a warning. Pluggy (used by pytest) currently uses this, so you’ll see warning in your tests until it’s updated.

Some other deprecations:

  • The asyncio policy system
  • PurePath.as_uri (use Path.as_uri() instead)
  • os.popen and os.spawn* are soft deprecated (no removal timeline)

Some things were removed, but most of them should have been producing warnings for a while. Remember to run your tests with warnings as errors turned on! These removals are things like ast classes that were replaced by ast.Constant in 3.8, some asyncio stuff, importlib.abc stuff, and some pkgutil stuff.

The remove of __package__ (replaced by __spec__.parent) was delayed to 3.15.

Other Developer changes

Other features include:

  • iOS testing and output improvements
  • Three argument pow() now considers __rpow__
  • Complex arithmetic update to match C99
  • -X importtime=2 added, tracks cached modules too
  • Slots are not wrapped if not overridden (small perf improvement)
  • Default multiprocessing method on Linux is now forkserver instead of fork
  • Some ctypes structure updates
  • Instances are reused in pdb, keeping instance data across breakpoints
  • Pickle default protocol is now 5.
  • Windows now include ABIFLAGS in sysconfig.get_config_Vars().

And CAPI had a lot of additions to clean it up and make it easier to use, like PyUnicodeWriter_*, iteration and conversion additions, integer API, checking for immortality, and more. There’s also a new PyConfig_* and PyInitConfig_* C API for getting and setting runtime configuration.

Final Words

If you are using GitHub Actions, the new and best way to add 3.14 is to use this:

- uses: actions/setup-python@v5
  with:
    python-version: "3.14"
    allow-prereleases: true

This works in a matrix, etc. too. If you want to try out free-threaded Python, that’s either "3.14t" or enable the free-threaded option.

So far, it’s been pretty easy to adopt. There’s a deprecation warning coming from pluggy that shows up in pytest in some cases, nox works out of the box. Tox has some issues that should be worked out soon. pybind11 has four failures we still need to solve. If you don’t depend on a binary package (like numpy), try it out!

Sources and other links


Posts in the Python Series2→3 3.7 3.8 3.9 3.10 3.11 3.12 3.13 3.14