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 <script>alert('x')</script>!</p>'
Notes:
- A
Templatehas no rendering__str__. That is deliberate: you cannot accidentally use one as a plain string. string.templatelib.convert(obj, conversion)applies the!s,!r,!asemantics for you when you write processors.Interpolationsupports 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 annotationskeeps 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 toannotationlib.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::SyntaxWarningto findreturn/break/continueinfinally(from a clean checkout, so cached.pycfiles do not skip compilation). - [ ] Search for code that reads
__annotations__directly or relies on annotation side effects; switch toannotationlib.get_annotations(). - [ ] On Unix platforms other than macOS (so, Linux),
ProcessPoolExecutornow usesforkserverinstead offorkby default. Check code that relied on inherited global state in workers. - [ ] Check that your C-extension dependencies publish 3.14 wheels (and
cp314twheels 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.zstdfor 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 theforkserverdefault 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
- Python Software Foundation, Python 3.14.0 release page (October 7, 2025): release date, feature list, experimental JIT in macOS and Windows binaries.
- Python docs, What's New In Python 3.14: all features in this post, free-threading 5-10% penalty, interpreter limitations, tail-call interpreter benchmarks, PEP 784 package details,
forkserverdefault, smaller changes. - PEP 779: Criteria for supported status for free-threaded Python (accepted June 16, 2025): phase II, supported but optional.
- Python docs, Python support for free threading:
Py_GIL_DISABLED,sys._is_gil_enabled(),PYTHON_GIL, GIL re-enabled for incompatible extensions, iterator thread-safety. - PEP 750: Template Strings and string.templatelib docs:
Template,Interpolation, attributes,convert(). - PEP 649: Deferred Evaluation Of Annotations Using Descriptors, PEP 749: Implementing PEP 649 and annotationlib docs: formats,
get_annotations(). - PEP 734: Multiple Interpreters in the Stdlib and concurrent.interpreters docs:
create(),exec(),call(),call_in_thread(), queues. - PEP 784: Adding Zstandard to the standard library and compression.zstd docs: API.
- PEP 758: Allow except and except* expressions without parentheses.
- PEP 765: Disallow return/break/continue that exit a finally block.
- PEP 768: Safe external debugger interface for CPython:
sys.remote_exec(),pdb -p, opt-out switches. - PEP 761: Deprecating PGP signatures for CPython artifacts.