Python 3.14.0 was released on October 7, 2025. It is one of the larger releases in a while, and unusually, several of the headline features change how you think about the language rather than just adding a module: a build without the GIL is now officially supported, there is a new kind of string literal that does not produce a string, and annotations are no longer evaluated when a function or class is defined.

Most of it is opt-in. Your 3.13 code will almost certainly run unchanged. But a few defaults moved (annotations, the ProcessPoolExecutor start method on Linux, new warnings in finally blocks), and those are the places where upgrades break.

This cheatsheet covers what is in 3.14.0, with a short example for each feature and a note on when it matters. Every example was run on a 3.14 interpreter. Everything here is taken from the official "What's New in Python 3.14" document and the PEPs; nothing is speculative.

1. The release at a glance

Feature PEP Where Opt-in?
Free-threaded build officially supported 779 (builds on 703) Separate build of CPython Yes, install the free-threaded build
Template strings (t"...") 750 string.templatelib Yes, new syntax
Deferred evaluation of annotations 649, 749 Language, annotationlib No, new default
Multiple interpreters in the stdlib 734 concurrent.interpreters, InterpreterPoolExecutor Yes
Zstandard compression 784 compression.zstd Yes
except A, B: without parentheses 758 Syntax Yes
SyntaxWarning for return/break/continue leaving finally 765 Compiler No, new warning
Safe external debugger interface 768 sys.remote_exec(), pdb -p Yes
No more PGP signatures for releases; Sigstore instead 761 Release process n/a
Experimental JIT in official Windows and macOS binaries - Interpreter Yes, experimental

2. Free-threaded Python is officially supported (PEP 779)

Python 3.13 shipped an experimental build of CPython with the GIL disabled (PEP 703). PEP 779, accepted by the Steering Council in June 2025, set the criteria for moving to "phase II": the free-threaded build is officially supported but still optional. It is not the default build. Phase III (making it the default) is a separate future decision.

What changed in 3.14, per What's New:

  • The PEP 703 implementation is finished, including C API changes, and temporary workarounds were replaced with permanent solutions.
  • The specializing adaptive interpreter (PEP 659) is now enabled in free-threaded mode.
  • The single-threaded performance penalty of the free-threaded build is now "roughly 5-10%, depending on the platform and C compiler used".

Getting it and checking it:

uv python install 3.14t        # the "t" suffix selects the free-threaded build
uv run --python 3.14t python -VV
# Python 3.14.0 free-threading build (...)
import sys
import sysconfig

# Does this build support free threading? (the recommended check for build decisions)
print(sysconfig.get_config_var("Py_GIL_DISABLED"))  # 1 on a free-threaded build

# Is the GIL actually disabled right now?
print(sys._is_gil_enabled())  # False when running without the GIL

CPU-bound threads now run in parallel:

from concurrent.futures import ThreadPoolExecutor

def work(n: int) -> int:
    return sum(i * i for i in range(n))

with ThreadPoolExecutor(max_workers=4) as ex:
    results = list(ex.map(work, [3_000_000] * 4))

On a 4-core machine this runs several times faster on the free-threaded build than on the default build, where the threads take turns holding the GIL.

Things to know before switching:

Topic What happens
C extensions not marked as free-threading compatible The GIL is re-enabled automatically on import, and a warning is printed
Forcing the GIL on PYTHON_GIL=1 or python -X gil=1
Shared iterators Not thread-safe: two threads reading one iterator may see duplicate or missing items
Your own code You need locks where you relied on the GIL to make compound operations look atomic
Packages Wheels must be built specifically for the free-threaded ABI (cp314t)

Rule of thumb: try it if you have CPU-bound work in threads and your dependencies publish free-threaded wheels. Keep the default build otherwise.

3. Template strings: t-strings (PEP 750)

A t-string looks like an f-string with a t prefix, but it does not produce a str. It produces a string.templatelib.Template object that keeps the static parts and the interpolated values separate. Your code decides how to combine them.

from string.templatelib import Template, Interpolation

name = "Camembert"
template = t"Ah! We do have {name}."

type(template)            # <class 'string.templatelib.Template'>
template.strings          # ('Ah! We do have ', '.')
template.values           # ('Camembert',)
template.interpolations   # (Interpolation('Camembert', 'name', None, ''),)
list(template)            # ['Ah! We do have ', Interpolation(...), '.']

Each Interpolation carries value, expression (the source text), conversion ('a', 'r', 's' or None) and format_spec:

i = t"{3.14159:.2f}".interpolations[0]
i.value, i.expression, i.conversion, i.format_spec
# (3.14159, '3.14159', None, '.2f')

The point is safe processing of untrusted values. A minimal HTML escaper:

from html import escape
from string.templatelib import Template, Interpolation

def html(template: Template) -> str:
    out = []
    for item in template:
        if isinstance(item, Interpolation):
            out.append(escape(str(item.value)))
        else:
            out.append(item)
    return "".join(out)

user = "<script>alert('x')</script>"
html(t"<p>Hello {user}!</p>")
# '<p>Hello &lt;script&gt;alert(&#x27;x&#x27;)&lt;/script&gt;!</p>'

Notes:

  • A Template has no rendering __str__. That is deliberate: you cannot accidentally use one as a plain string.
  • string.templatelib.convert(obj, conversion) applies the !s, !r, !a semantics for you when you write processors.
  • Interpolation supports pattern matching: case Interpolation(value, expression, conversion, format_spec):.
  • Natural use cases: HTML and SQL builders that escape or parameterize values, structured logging that keeps the values as fields, and domain-specific mini-languages.
Literal Produces Use when
f"..." str, evaluated now You want the final text
t"..." Template, values captured now, rendering deferred to your code Values need escaping, parameterizing or structured handling
string.Template("$x") Old $-substitution class Templates come from users or config files

4. Deferred evaluation of annotations (PEP 649 and PEP 749)

Annotations on functions, classes and modules are no longer evaluated eagerly. They are stored in special annotate functions and evaluated only when something asks for them. Forward references now just work without quotes:

class Node:
    next: Node | None              # no NameError, no quotes needed

    def link(self, other: Node) -> None: ...

The new annotationlib module lets you choose how to read them:

from annotationlib import get_annotations, Format

def f(x: Undefined) -> int: ...

get_annotations(f, format=Format.STRING)
# {'x': 'Undefined', 'return': 'int'}

get_annotations(f, format=Format.FORWARDREF)
# {'x': ForwardRef('Undefined', owner=<function f ...>), 'return': <class 'int'>}

get_annotations(f, format=Format.VALUE)   # same as f.__annotations__
# NameError: name 'Undefined' is not defined
Format Returns Good for
Format.VALUE Evaluated objects; raises if a name is missing Runtime type checking, dataclass-like libraries
Format.FORWARDREF Objects where possible, ForwardRef for missing names Tools that must not fail on undefined names
Format.STRING Source-like strings Documentation tools, IDEs

Migration notes:

  • from __future__ import annotations keeps its old behavior (annotations stored as strings). It still works; you do not need to remove it today.
  • Code that reads __annotations__ directly from a class __dict__ should switch to annotationlib.get_annotations().
  • Side effects inside annotations no longer run at definition time. If anything relied on that, it will now run later or never.

5. Multiple interpreters in the standard library (PEP 734)

Subinterpreters have existed in the C API for decades. 3.14 exposes them to Python code in concurrent.interpreters. Each interpreter is isolated (its own modules, its own GIL), so they can run Python code truly in parallel inside one process, without free-threading.

from concurrent import interpreters

interp = interpreters.create()
interp.exec('print("hello from another interpreter")')

def square(n: int) -> int:
    return n * n

interp.call(square, 7)            # 49, runs in the current OS thread
t = interp.call_in_thread(square, 8)
t.join()
interp.close()

Communication goes through queues (interpreters.create_queue()), which behave like queue.Queue.

For pool-style work, use the new executor in concurrent.futures:

from concurrent.futures import InterpreterPoolExecutor

def cpu(n: int) -> int:
    return sum(i * i for i in range(n))

if __name__ == "__main__":
    with InterpreterPoolExecutor(max_workers=4) as ex:
        print(list(ex.map(cpu, [10_000] * 4)))

Current limitations, as listed in What's New: interpreter startup is not optimized yet, each interpreter uses more memory than necessary, there are few options for truly sharing objects (besides memoryview), and many third-party extension modules on PyPI are not yet compatible (all standard library extension modules are).

Model Parallel CPU? Isolation Startup and memory Sharing data
Threads, default build No (GIL) None Cheap Shared memory
Threads, free-threaded build Yes None Cheap Shared memory, needs locks
InterpreterPoolExecutor Yes Per interpreter Medium Queues, memoryview
ProcessPoolExecutor Yes Per process Expensive Pickling, shared memory

6. Zstandard and the new compression package (PEP 784)

compression.zstd adds Zstandard support, via bindings to Meta's zstd library, with an API that will feel familiar from gzip, bz2 and lzma:

from compression import zstd

data = b"hello world " * 1000
packed = zstd.compress(data, level=10)
assert zstd.decompress(packed) == data

with zstd.open("events.jsonl.zst", "wt") as f:   # text mode works like gzip.open
    f.write('{"event": "signup"}\n')

Also available: ZstdCompressor and ZstdDecompressor for streaming, train_dict() and ZstdDict for dictionary compression of many small, similar payloads, and CompressionParameter for options such as checksum_flag.

The new compression package also re-exports the existing modules as compression.lzma, compression.bz2, compression.gzip and compression.zlib. These are now the preferred import names, but the old names are not deprecated, and any removal will happen no sooner than five years after 3.14.

7. Syntax and warnings: except, finally

PEP 758: except without parentheses. When you catch several exception types and do not use as, the brackets are optional:

try:
    connect()
except TimeoutError, ConnectionRefusedError:
    retry()

# With "as", parentheses are still required:
except (TimeoutError, ConnectionRefusedError) as exc:
    log(exc)

The same applies to except*.

PEP 765: control flow leaving finally. The compiler now emits a SyntaxWarning for return, break or continue that exits a finally block, because these silently swallow in-flight exceptions:

def load():
    try:
        return read_config()
    finally:
        return {}   # SyntaxWarning: also hides any exception from read_config()

If your CI runs with warnings as errors, this is the change most likely to fail a build on upgrade.

8. Debugging and tooling

PEP 768: safe external debugger interface. Debuggers and profilers can attach to a running process without stopping it or injecting unsafe code. From Python, sys.remote_exec(pid, script_path) asks process pid to run a script at a safe point. The headline user of this is pdb:

python -m pdb -p 1234     # attach pdb to running process 1234

It can be disabled with PYTHON_DISABLE_REMOTE_DEBUG, -X disable-remote-debug, or at build time with --without-remote-debug. In production containers, decide deliberately whether you want it on.

asyncio introspection. Inspect what a running asyncio program is doing:

python -m asyncio ps 1234       # flat list of tasks
python -m asyncio pstree 1234   # async call tree

The REPL now highlights syntax in color by default (PYTHON_BASIC_REPL=1 gives you the old one), and argparse.ArgumentParser(color=True) colors help output.

Better error messages, for example keyword typos:

>>> whille True:
SyntaxError: invalid syntax. Did you mean 'while'?

9. Smaller changes worth knowing

Change Example
map() gets strict= like zip() map(f, a, b, strict=True) raises ValueError on unequal lengths
functools.Placeholder for partial() partial(str.replace, Placeholder, "-", "_")("a-b") returns 'a_b'
Max-heap functions in heapq heapify_max, heappush_max, heappop_max, heapreplace_max, heappushpop_max
UUID versions 6, 7 and 8 uuid.uuid7() for time-ordered IDs
pathlib.Path copy and move src.copy(dst), src.copy_into(dir), src.move(dst), src.move_into(dir)
python -m json Preferred over python -m json.tool, which is soft deprecated
http.server HTTPS --tls-cert and --tls-key options
date.strptime() and time.strptime() Parse directly into date or time

10. Performance and build notes

  • Experimental JIT. The official macOS and Windows release binaries include an experimental just-in-time compiler. Treat it as something to benchmark, not a default.
  • Tail-calling interpreter. A new interpreter that uses tail calls between small C functions per opcode. With newer compilers (Clang 19+ on x86-64 and AArch64), preliminary benchmarks show a geometric mean of 3-5% faster on pyperformance. It is a build option (--with-tail-call-interp), so whether you get it depends on how your Python was built.
  • Free-threaded overhead is 5-10% on single-threaded code (section 2). If you do not use threads for CPU work, the default build is still faster.

11. Upgrade checklist

  • [ ] Run your test suite with -W error::SyntaxWarning to find return/break/continue in finally (from a clean checkout, so cached .pyc files do not skip compilation).
  • [ ] Search for code that reads __annotations__ directly or relies on annotation side effects; switch to annotationlib.get_annotations().
  • [ ] On Unix platforms other than macOS (so, Linux), ProcessPoolExecutor now uses forkserver instead of fork by default. Check code that relied on inherited global state in workers.
  • [ ] Check that your C-extension dependencies publish 3.14 wheels (and cp314t wheels if you try free-threading).
  • [ ] If you verified releases with PGP, switch to Sigstore (PEP 761).
  • [ ] Try t-strings anywhere you build HTML, SQL or shell commands from user input.
  • [ ] Consider compression.zstd for new storage formats and logs.

Why this matters

  • Parallelism has real options now. Free-threaded threads, isolated interpreters and processes cover different trade-offs, all in the standard distribution.
  • t-strings make the safe path the easy path. Escaping and parameterization can be built into the type instead of remembered by the caller.
  • Annotations stop being a startup cost and a source of NameError. Forward references and circular type hints become ordinary code.
  • Some defaults changed. Warnings in finally, lazy annotations and the forkserver default are where upgrades will surprise you.

Final Thoughts

3.14 is a release to adopt in two steps. Upgrading the default build is low risk: run the tests with warnings enabled, fix what finally and annotation changes flag, and you get better errors, zstd, the new REPL and pdb attach for free.

The free-threaded build, subinterpreters and t-strings are different: they are new ways to write programs, and they reward a small, deliberate experiment first. Pick one CPU-bound job and one place where you build strings from untrusted input, try the new tools there, and measure.

Sources