The First Ever No-GIL Python: What’s New in Python 3.13?

We’ve already written about Python 3.12. and explored whether it really lives up to Guido van Rossum’s promise of being twice as fast as Python 3.11. But now, let’s focus on the current update — Python 3.13. What new features does it offer, and should you consider upgrading your projects to this version?
Python 3.13 update. The end of GIL?

Unexpected delay in release

The official release of Python 3.13, originally scheduled for 1 October, has been pushed back to 7 October 2024. Work on Python 3.13 began in May 2023. By May 2024, it had stopped receiving new changes except for bug fixes and documentation improvements.

Python’s development cycle follows well-defined stages, with evolving tasks and responsibilities for the core Python developers at each stage. The versions are based on the "major.minor.micro" principle. In addition to final releases, Python also has pre-release versions labeled Alpha, Beta, and Release Candidate. These versions are intended for testing by experienced users, not for use in production environments.

Development stages

Depending on the stage of the current Python version, the requirements for making changes to the source code also vary.

Python 3.13 development stages

This approach helps the development team work together and keep each new Python version stable. Why the delay?

The delay was needed because changes to the incremental cyclic garbage collector caused a drop in performance. Therefore, the team decided to roll back the garbage collector changes and apply fixes to ensure optimal performance in Python 3.13. As a result, another release candidate (3.13.0rc3) was issued, which is expected to be the final version of Python 3.13.0. The new garbage collector won't be included in patch releases, as adding new functionality would violate standard release processes. It will be reintroduced in future minor versions (3.14, 3.15, etc.).

Despite this adjustment, Python 3.13 remains fully functional, with no changes to the ABI since the beta version.

Enhanced interactive REPL interpreter

The interactive REPL (Read-Eval-Print Loop) interpreter is now based on PyPy and has been upgraded. The new features will be available when users launch the REPL from the interactive terminal.

  1. Multi-line editing with history preservation

Imagine declaring a decorator directly in the REPL.

Imagine then realizing you made a mistake, returning the `func` object instead of the `wrapper` in the decorator. Reaching this conclusion isn’t straightforward based on the previous example. So, to demonstrate that the `wrapper` won’t be called, let's try again and add a print statement that outputs "decorator" before executing the function we're wrapping in the `wrapper`.

Run it again and you'll only see "Hello World!" The expected "decorator" output didn't happen. What do you do now? Press the up arrow a few times and change `return func` to `return wrapper`.

Before Python 3.13, making adjustments to your function or class declaration (like the decorator in our example) was a hassle. Pressing the up arrow only allowed you to edit a single line, not multiple lines. Now, thanks to multi-line editing and history preservation, everything has changed.

Press the up arrow once:

Press the up arrow a second time: 

2. You can use native support for REPL commands like `help()`, `exit()`, and `quit()` without needing to call them as functions. Just use `help`, `exit`, or `quit`   

3. Hints and tracebacks now come with color highlighting enabled by default

4. Interactive help view with F1, featuring a separate command history

5. View history using F2

6. "Insert mode" with F3, which makes it easier to paste large blocks of code

To disable the new interactive shell, set the environment variable `PYTHON_BASIC_REPL`.

Experimental feature: disabling the global interpreter lock (GIL)

The new Python 3.13 release marks a major step forward in addressing the core issue of the Python interpreter — the GIL. While there is still discussion about removing the GIL completely, the experimental option to disable the global interpreter lock is now available.

Is the GIL a serious issue? Recall Larry Hastings' words at PyCon 2015: "I would say that the design decision to include the GIL is one of the things that made Python as popular as it is today. Therefore, I disagree with those who say that the GIL is terrible and should never have been added. No, I don't think so. The reason Python became successful is that it has the GIL."

Eight years on from those remarks, it is clear that Python is moving towards the removal of the GIL. A separate article is needed to address the implications of this change, including possible security issues and whether the absence of the GIL is truly needed by developers. For now, let me highlight what has changed with a quote from the documentation and provide an example:

“The new version of CPython includes experimental support for execution without the GIL, allowing for free-threaded execution. This feature is currently experimental and is not enabled by default. To utilize free-threading mode, you need to use a different executable file, typically named `python3.13t` or `python3.13t.exe`. Precompiled binary files labeled as "free-threaded" can be installed through official installers for Windows and macOS. It's also possible to build CPython from source using the `--disable-gil` option.
Free-threaded execution allows for full utilization of CPU power by enabling threads to run in parallel across multiple CPU cores. While not all programs will automatically benefit from this, those designed with threading in mind will run faster on multi-core systems. Since this feature is experimental, users may encounter bugs and significant performance drops when running on a single core. In free-threaded versions of CPython, users can enable the GIL during execution by using the environment variable `PYTHON_GIL` or the command-line option `-X gil=1`.
To check if the current interpreter supports free-threading mode, you can use the command `python -VV` or check the value in `sys.version`, where it will indicate "experimental free-threading build." A new function, `sys._is_gil_enabled()`, is also available to verify whether the GIL is indeed disabled in the current process.”

Let's take a look at examples using `ThreadPoolExecutor` and `ProcessPoolExecutor`:

And

Local executions using `ThreadPoolExecutor` and `ProcessPoolExecutor` in version 3.12 or 3.13 with the GIL yield the following results

For ThreadPoolExecutor:

For ProcessPoolExecutor:

Version 3.13 without GIL produces different results. 

For ThreadPoolExecutor:

For ProcessPoolExecutor:

Let's summarize the total execution time:

Python 3.13 with GIL vs Python 3.13 without GIL

The results are clear: MultiThreading is significantly faster, while MultiProcessing is 20% slower. The reasons for this behavior and whether it will be resolved are unclear. I encourage you to use this as an opportunity to experiment with free-threaded execution mode on your own.

In the meantime, it is highly recommended to continue using the GIL-enabled interpreter for your projects, as version 3.12 remains stable and has not undergone significant performance changes.

Experimental Just-In-time (JIT) compilation

A JIT (Just-In-Time) compiler is a tool that compiles Python code into machine code “on the fly,” during program execution. This compiler generates code quickly and is easy to maintain, delivering significant speed improvements in program execution. One of the most commonly used JIT compiler solutions is Numba. However, today we are discussing the ongoing efforts to integrate JIT into CPython.

We do not yet know how the JIT compiler will work in Python. PEP-744 confirms that it will be based on a Copy-and-Patch architecture, compiling Python bytecode into machine code using LLVM. However, this PEP is still in draft status. It states the following:

“If you are a Python programmer or an end user, nothing changes for you. No one should distribute CPython interpreters with JIT support while this feature remains experimental. When it is no longer experimental, you may notice slightly better performance and slightly increased memory usage, but you should not see any other changes.”

Improvements to error messages

I have noticed a clear trend over the past few years: each fall, the new version of Python brings useful enhancements to error messages. Let's look at an example where calling the function f() causes a NameError due to the absence of the variable string. The new output error messages make it easy to trace the sequence of function executions and the error tree.

Example:

The next improvement in error messaging involves enhanced notifications when importing a standard module that has the same name as the module being executed.

Here's how it appeared in Python 3.12:

And here’s how it looks in Python 3.13:

Optimization of documentation strings (docstrings)

Memory usage has been reduced by eliminating leading whitespace in documentation strings. 

Example:

No need to worry about variables you declare with triple quotes. The optimization only affects documentation comments and does not impact your multi-line string declarations.

Read-only elements in TypedDicts 

Python now allows you to declare fields in a TypedDict as read-only using the new `ReadOnly` type. This is a significant improvement that enhances type safety and prevents unintended modifications in critical data structures.

The documentation provides the following example:

The `title` field is marked as `ReadOnly[str]`, meaning its value cannot be modified after the object is created. If someone tries to change it, a static type checker like `mypy` will flag an error before the program runs. This is particularly useful for APIs where it's crucial that input data remains immutable to prevent unexpected failures or unintended modifications.

Deprecated decorator

The `deprecated` decorator is now available, making it much easier to identify and fix outdated code. You can mark functions or classes with the `deprecated` decorator to indicate they are obsolete and no longer recommended, while also suggesting alternatives.

This is crucial for maintaining clean and up-to-date code. Deprecated functions and classes often remain in projects and can cause errors or compatibility issues in future versions. Using `@deprecated` ensures developers receive warnings when such elements are used, making it easier to detect and update outdated code.

Example:

Deprecation of Python 3.8

With the release of Python 3.13, which promises support until October 2029, the security support for Python 3.8 has officially ended. Why is that?

Python has supported each version for approximately five years, and it has been that long since the release of 3.8. Full support lasts for around two years, including feature updates and bug fixes. Once this period ends, the version moves into an extended support phase, during which only critical security updates are released. The main reason for this approach is that maintaining older versions is a waste of resources and hinders the development of new versions.

Python developers can implement new features and improvements quickly and efficiently with two years of active support, without being bogged down by the need to maintain older versions. The development team must invest significant resources to support old versions, so developers and companies are encouraged to migrate to newer versions to benefit from the latest features and performance enhancements.

Here’s how the release cycle looks:

Python 3.13 release cycle

So, from the release of the current version, additional updates and bug fixes in Python will only be available for Python 3.12 and Python 3.13. For developers who haven’t yet migrated their projects, it’s becoming increasingly important to start moving away from the now outdated versions 3.6, 3.7, and now even 3.8, to supported versions (preferably straight to 3.12 😁).

Conclusion

Python 3.13 was released later than expected due to the complexity of changes, particularly those related to the experimental GIL removal. This new approach required extensive testing and stabilization. So, what has changed?

1. The interpreter experience has been improved, making it more interactive and user-friendly, with new features and enhanced support for "on-the-fly" coding.

2. The Global Interpreter Lock (GIL) is no longer mandatory, thanks to the experimental feature. This allows programs to use multithreading more efficiently, opening the door to better performance on multicore processors.

3. Errors are now displayed with greater detail and clarity, significantly easing debugging and making Python more developer-friendly.

4. Python documentation optimization allows docstrings to be used more efficiently. This affects both the code execution speed and the ease of working with documentation.

5. The new ReadOnly feature for TypedDict ensures that certain fields remain unchanged, adding security and predictability when working with structured data.

6. Python now has a built-in `deprecated` decorator, which simplifies the process of identifying outdated code, allowing developers to update their projects more efficiently.

💡
The changes introduced in Python 3.13 are not groundbreaking. Many of them focus more on experimentation and testing than offering functional advantages. However, staying up-to-date with these trends and leveraging new technologies will give you a competitive edge.

Update Python 3.12: Is it 2 times faster? Key changes
Hi! My name is Oleksii. I am a first-year master’s student at the Kyiv Polytechnic Institute, and I have been programming in Python for about five years. This is already the third article in the #mainfeatures series, highlighting the new features that come with the latest Python versions. Let’s talk

If you missed the previous releases or wish to revisit the details, here is link to a comprehensive article highlighting Python 3.12 changes: