I took a quick look at the new features in Python 3.14. To try the code below on your local machine, install uv and run the REPL with the following command:

uv python upgrade 3.14
uvx [email protected]

PEP 750: Template Strings

Template strings (t-strings for short) were a proposal to address a significant shortcoming in existing f-strings, and it’s now ready for use with version 3.14. First, let me show you the problem:

>>> name = "Gökmen"
>>> f_hello = f"Hello, {name}"
>>> t_hello = t"Hello, {name}"
>>> type(f_hello)
<class 'str'>
>>> type(t_hello)
<class 'string.templatelib.Template'>

Since the type of f_hello is str, extra code was needed to understand what the variables were or which parts were static. However, when the type is Template, all this information is accessible:

>>> dir(t_hello)
['__add__', '__class__', '__class_getitem__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'interpolations', 'strings', 'values']
>>> t_hello.values
('Gökmen',)
>>> t_hello.strings
('Hello, ', '')
>>> t_hello.interpolations
(Interpolation('Gökmen', 'name', None, ''),)

Why was this needed? It makes string operations on dynamic values more practical:

>>> from string.templatelib import Interpolation
>>> "".join([part.value.upper() if isinstance(part, Interpolation) else part for part in t_hello])
'Hello, GÖKMEN'

With Template, you can even build a simple Jinja clone.

PEP 649, PEP 749: Eliminating NameError in Annotations

The following code gives a NameError in Python 3.13 because we are trying to define the type of a while X is not yet defined:

>>> a: X | None = None
... class X: ...
...
Traceback (most recent call last):
  File "<python-input-0>", line 1, in <module>
    a: X | None = None
       ^
NameError: name 'X' is not defined

To solve this problem, we either had to make X a string or use __future__.annotations. Now, neither is necessary:

>>> a: X | None = None
... class X: ...
...
>>>

PEP 734: New Module for Using Multiple Interpreters: interpreters

It is now possible to run Python code through multiple interpreters within the same process without getting lost in the depths of the C-API, staying within Python code. Normally, all sub-interpreters you start under the main interpreter are isolated from each other. Even if you started the same Python code in 100 interpreters, it didn’t contribute to performance as long as there was no communication or data transfer between them. Now, this new module makes communication possible through shared objects. Perhaps I can demonstrate this later by optimizing RAYT, but for now, I’m sharing an example code from here:

import interpreters
from mymodule import load_big_data, check_data

numworkers = 10
control = interpreters.create_queue()
data = memoryview(load_big_data())

def worker():
    interp = interpreters.create()
    interp.prepare_main(control=control, data=data)
    interp.exec("""if True:
        from mymodule import edit_data
        while True:
            token = control.get()
            edit_data(data)
            control.put(token)
        """)
threads = [threading.Thread(target=worker) for _ in range(numworkers)]
for t in threads:
    t.start()

token = 'football'
control.put(token)
while True:
    control.get()
    if not check_data(data):
        break
    control.put(token)

PEP 758: No Longer Necessary to Use Parentheses for Multiple Exceptions

This was one of the most common syntax errors I made. Now, writing exceptions without using parentheses is valid syntax:

>>> try:
...     x()
... except NameError, ValueError:
...     print("achtung!")
...
achtung!

PEP 765: Plan to Deprecate return/break/continue Inside finally

To maintain backward compatibility, we only get a SyntaxWarning for now. Since finally always executes, using return within it was quite confusing:

>>> def bool_return():
...     try:
...         return True
...     finally:
...         print("you tried to return True but you will also see this message?")
...         return False
...
<python-input-3>:6: SyntaxWarning: 'return' in a 'finally' block
>>> bool_return()
you tried to return True but you will also see this message?
False

By the way… The REPL has really turned into a text-based editor; the syntax highlighter works beautifully.

Python 3.14, REPL Syntax Highlighter

PEP 768: Ability to Debug Without Adding Breakpoints

This is a bit like open-heart surgery, I guess; in production and production code, not even a single piece of code related to the testing and development process is allowed to be committed to the repository. Services like Sentry are generally used for this, and the best thing to do right now is to use logging effectively. Rarely, we might wish for easier debugging. PEP 768 could be an alternative for that.

A function named remote_exec helps us with this. The first parameter of this function is the PID number of the Python process we want to debug:

import sys
import uuid

# Execute a print statement in a remote Python process with PID 12345
script = f"/tmp/{uuid.uuid4()}.py"
with open(script, "w") as f:
    f.write("print('Hello from remote execution!')")

try:
    sys.remote_exec(12345, script)
except Exception as e:
    print(f"Failed to execute code: {e}")

Other Innovations and Python 3.9

The Free-Threaded mode introduced in 3.13, garbage collection, the REPL, and standard modules have been improved. Error messages can now offer suggestions for typos (if you type ‘whillle’, it asks, ‘did you mean “while”?’).

By the way, with every new major release, the most significant thing holding us back from these innovations is the continued support for older versions. Python 3.9 will no longer receive security updates. I think it’s time to stop supporting 3.9 in our projects. By doing so:

  1. We will be able to use Structural Pattern Matching (match - case). Take a look here.
  2. We will be able to use parentheses to manage multiple contexts with a single with statement.
  3. Pipe usage like bool | None will be valid, so you can use pipes between types instead of Union[bool, None].

If you are looking for a guide to keeping Python projects up to date, you can check out this article of mine:

Resources