Python 3.13

Python 3.13 betas are out, which means the features are locked in. For the first time in thirty years, Python has a new, more colorful REPL! There’s also a no-GIL compile-time option (free-threaded), an optional JIT, some new typing features, and better error messages (again).


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

Interpreter improvements

PyPy’s REPL was brought over to CPython. The old REPL was deeply linked to the internals of the interpreter, while the new one is written in Python and finally makes some long requested features possible! The new interpreter has colorized prompts, multiline editing, three new keyboard shortcuts (F1: help, F2: history, F3: paste mode), and repl-specific commands no longer need trailing parentheses (like help, exit, and quit)!

Tracebacks are now colorized by default, as is doctest output. FORCE_COLOR/NO_COLOR are respected.

New error message updates include:

  • Trying to access an attribute from a local module overlapping a global/installed one will now mention this as a possible problem.
  • Mistyped keyword args now suggest possible matches.

And breakpoint() now enters the debugger on the line it it given, rather than the line after, meaning it works at the end of a function now.

Free-threading

Python can now be built in free-threading mode, and wheels can be built for free-threading Python (requires pip 24.1b1 or newer, or uv to install).

When built in this mode, CPython no longer has a GIL, and supports true multithreading. This will likely require some updates from compiled libraries (supporting free-threading wheels) and some code that happened to work because it was relying on the GIL might require proper multithreading constructs like locks and such.

If you use a free-threaded build, you can manually enable the GIL with PYTHON_GIL or -X gil. You can check with sys._is_gil_enabled() and check for free-threaded Python with sysconfig.get_config_var("Py_GIL_DISABLED"). Python will enable the GIL if an extension module doesn’t declare free-threaded support.

JIT compiler

CPython 3.13 comes with a JIT compiler (disabled by default for now). It doesn’t make things much faster yet, but the infrastructure is there. Just like the Specializing Adaptive Interpreter, it will slowly gain support for more Python and in theory make a future version faster.

It works using a copy-and-patch approach. When building Python, you have to have LLVM available (even if you are not compiling with LLVM - LLVM has a tail call attribute that is required for this to work that is missing from GCC), and it will generate machine code for a set of Tier 2 IR (a new construct a level higher than the current Tier 1 IR introduced in CPython 3.11). At run time, if a code path is hot enough, it will use these machine codes instead of running the interpreter.

Static Typing

As always for non-syntax improvements, typing updates are available for older (3.8+) Python’s in typing-extensions.

TypeIs

A new typing.TypeIs augments typing.TypeGuard. For example:

def is_string(x: int | str) -> TypeIs[str]:
    return isinstance(x, str)


if not is_string(value):
    typing.reveal_type(value)
# Revealed type is 'int'
def is_string(x: int | str) -> TypeGuard[str]:
    return isinstance(x, str)


if not is_string(value):
    typing.reveal_type(value)
# Revealed type is 'int | str'

This prints int, but if we used the older TypeGuard instead, it would have printed int | str, because a TypeGuard doesn’t infer anything if the function is False. With TypeIs, you can model the common case of a function returning true if it’s a type, and also if it’s not that type. See more.

Generic defaults

Another static addition is Generics can finally have defaults. This can be done using the new default= keyword for TypeVar and friends for backward compatibility, or as an added syntax to Python 3.12’s generics syntax (requires 3.13). For example:

@dataclass
class Box[T = int]:
    value: T | None = None
T = TypeVar("T", default=int)


@dataclass
class Box(Generic[T]):
    value: T | None = None

If you make a new Box() without explicitly setting the template parameter or passing a value, then the type will be int. The most notable use standard library use case is for Generator; most of the time, the second and third arguments are None, but currently you had to specify them anyway. Now they can have defaults.

def simple_generator() -> Generator[int]:
    yield from range(5)
def simple_generator() -> Generator[int, None, None]:
    yield from range(5)

(Note that Generator is a more correct type annotation for a generator function than Iterator, which is slightly different.)

Deprecated

Added to the warnings module, @warnings.deprecated("msg") is a type-checker supported way to add deprecation warnings to a function or class. It’s backport is @typing_extensions.deprecated(). You can also set the category (PendingDeprecationWarning, DeprecationWarning, and FutureWarning are the three levels) and stacklevel as well.

Along with this, deprecated= parameters were added to argparser’s methods to indicate deprecated parameters and subcommands.

Other typing features

A few other features:

  • typing.get_protocol_members() gets the members of a Protocol.
  • typing.is_protocol() checks to see if a class is a Protocol.
  • typing.ReadOnly can be used to mark items of a TypedDict as read only.

Other features

Other features include:

  • process_cpu_count() added to places where cpu_count() is provided.
  • Warning if bool passed instead of file handle.
  • configparser can be configured to handle items without a section.
  • .name and .mode attributes added for compressed files.
  • glob.translate() produces a regex from a shell-style glob.
  • itertools.batched() now has a strict= parameter.
  • math.fma() added for fused multiply adds, avoiding intermediate precision loss.
  • pathlib.Path.from_uri() constructor for file URIs.
  • pathlib.PurePath.full_match() supports shell style wildcards including **.
  • pathlib.Path.glob() and rglob() return files too, instead of just directories, if a pattern ends in **.
  • python -m random is a new CLI for random numbers.
  • dbm has a sqlite3 backend now.

Removals and deprecations

The remaining “dead batteries” have been removed; 19 modules that were very rarely used. These are: aifc, audioop, cgi, cgitb, chunk, crypt, imghdr, mailcap, msilib, nis, nntplib, ossaudiodev, pipes, sndhdr, spwd, sunau, telnetlib, uu and xdrlib.

optparse and getopt are soft deprecated; meaning they should not be used in new code, but do not have a removal scheduled. Other deprecations include a few items in typing like AnyStr, no_type_check_decorator(), io, re, and keyword arguments to NamedTuple. Also forgetting or using None for the value in the functional syntax for NamedTuple or TypedDict is deprecated. 2to3 has been removed. EntryPoint’s __getitem__ access has been removed. Undocumented Logger.warn() has been removed (deprecated since Python 3.3); you should be using .warning() instead. Path’s can’t be used as context managers (was a no-op since 3.9).

Quite a few previously deprecated things are going to be removed in 3.14 (Python π?), so make sure you are enabling all warnings as errors in your test suites! Things like ast.Num and friends (use ast.Constant), importlib.abc.Traversable (was moved), shutil.rmtree()’s onerror= (use onexc= instead)

Other Developer changes

One key new feature is a new 2 year “full-support” window; previous versions of Python have a 1.5 year full support window. This means bugfixes and new binaries can be produced for a larger portion of the 5 year support window.

Other features include:

  • The garbage collector is incremental now.
  • iOS is a tier 3 supported platform now.
  • Indents are now stripped from docstrings. Saves space.
  • Some deprecated importlib.resources functions were undeprecated.
  • python -m venv <PATH> now adds a .gitignore to the created venv.
  • Classes have new __firstlineno__ and __static_attributes__ attributes populated by the compiler.
  • Some changes to spawning processes, using os.posix_spawn more often, should speed up FreeBSD and Solaris.
  • Default time resolution is better on Windows.
  • PYTHON_PERF_JIT_SUPPORT allows integration with Perf without frame pointers (3.12 added PYTHON_PERF_SUPPORT using frame pointers)

Not a specific feature, but WASI is now the official flavor of WebAssembly actively supported, and Emscripten is relegated to Pyodide.

Final Words

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

- uses: actions/setup-python@v4
  with:
    python-version: "3.13"
    allow-prereleases: true

This works in a matrix, etc. too. If you want to try out free-threaded Python, it was shipped in manylinux on May 14, 2024 and you can use it with:

jobs:
  manylinux:
    name: Manylinux on 🐍 3.13 • Free-threaded
    runs-on: ubuntu-latest
    timeout-minutes: 40
    container: quay.io/pypa/manylinux_2_28x86_64:latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Prepare venv
        run: python3.13t -m venv .venv

      # etc

We are working on preparing support for tools like cibuildwheel, and this will be updated when it is ready. The new free-threading variant will pose some challenges that we’ll be working through. Be sure to use the pip 24.1 beta or newer for the free-threading variant, or use uv.

Sources and other links


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

comments powered by Disqus