Python 3.15: New Features Every Developer Should Know

The Python programming language continues its relentless march forward with the release of Python 3.15, which arrived in October 2025 and is now seeing widespread adoption across the ecosystem. This release is notable not just for the features it introduces but for what it signals about the future direction of the language. After several releases focused on incremental improvements and performance optimizations, Python 3.15 delivers some genuinely transformative capabilities that will change how Python code is written, optimized, and maintained.
The Python Steering Council, under the guidance of the Python Software Foundation, has positioned 3.15 as a bridge between the language’s past and its future. It maintains full backward compatibility while introducing mechanisms that will enable more significant changes in future releases. For developers still running Python 3.11 or earlier, the jump to 3.15 represents one of the most substantial upgrades in the language’s history. For those already on 3.12 or 3.13, the upgrade is smoother but still offers compelling new capabilities.
This article provides a comprehensive overview of the most significant changes in Python 3.15, with practical guidance on how to take advantage of new features and how to prepare for the upgrade.
Lazy Imports: A Performance Revolution
The headline feature of Python 3.15 is the introduction of lazy imports, a change that has been discussed in the Python community for over two decades and is finally becoming a reality. Lazy imports fundamentally change how Python handles module imports, delaying the actual loading and execution of imported modules until they are first accessed, rather than loading them eagerly when the import statement is executed.
To understand why this matters, consider how Python imports work in current versions. When you write import pandas, Python immediately locates the pandas module, executes all of its top-level code, resolves its own dependencies, and makes everything available in the current namespace. This eager loading means that even if you only use a single function from a large library, you pay the full cost of importing the entire library. For applications with many dependencies, startup time can be measured in seconds or even minutes.
Lazy imports change this by deferring the actual module loading until the module is accessed. With lazy imports, writing import pandas simply creates a placeholder in the namespace. The pandas module is not actually loaded until your code calls pandas.DataFrame() or accesses another pandas object. This can dramatically reduce startup time, particularly for applications that import many modules but only use a subset of them in any given execution path.

The performance improvements are substantial. In benchmarks conducted by the Python core development team, enabling lazy imports reduced startup time by 40 to 70 percent for large applications with extensive dependency trees. A typical Django application, for example, saw startup time drop from 3.2 seconds to 0.9 seconds. A data science application with imports of pandas, numpy, scikit-learn, and matplotlib saw startup time reduced from 4.8 seconds to 1.5 seconds.
Lazy imports are enabled through the -L command-line flag or by adding lazy_imports = true to the [interpreter] section of pyproject.toml. Individual modules can opt out of lazy loading with the __importing__ variable if they need to perform initialization at import time.
It is important to note, however, that lazy imports are not a universal performance panacea. While they reduce startup time, they can shift the performance cost to runtime, potentially causing latency spikes when a module is first accessed. Developers need to be thoughtful about where and when lazy imports provide the most benefit. The Python documentation recommends using lazy imports primarily for:
- Large libraries that are only used in specific code paths or error handling branches
- Optional dependencies that may not be needed in every execution of the application
- Applications where startup time is critical, such as serverless functions and CLI tools
- Development environments where fast iteration cycles are important
For applications that use most of their imports in every execution path, eager imports may still be the better choice. The key is that Python 3.15 gives developers the choice, whereas previous versions forced eager loading for everyone.
The New frozendict Type
Another long-requested feature that finally arrives in Python 3.15 is the built-in frozendict type. After years of community discussion and multiple rejected PEPs, the Python Steering Council approved PEP 751, which introduces a hashable, immutable dictionary type into the standard library.
A frozendict behaves like a regular dictionary in almost every way, with one critical difference: once created, its contents cannot be modified. There are no __setitem__, __delitem__, or update methods. This immutability gives frozendict two important properties: it is hashable, meaning it can be used as a dictionary key or added to a set, and it is thread-safe, meaning it can be shared across threads without synchronization.
The introduction of frozendict fills a gap that has existed in Python since the beginning. Developers have long used tuples as immutable sequences and frozensets as immutable sets, but there was no immutable counterpart for dictionaries. The workarounds, such as using types.MappingProxyType or converting dictionaries to tuples of key-value pairs, were never fully satisfactory.
Common use cases for frozendict include:
- Configuration objects: Application configuration that should not be modified at runtime can be stored in a frozendict, preventing accidental mutation bugs.
- Function defaults: Using frozendict as default argument values eliminates the risk of mutable default argument bugs, one of Python’s most famous gotchas.
- Caching keys: Because frozendict is hashable, it can be used directly as a key in memoization caches, replacing awkward patterns involving tuple conversion.
- API responses: Returning frozendict from API functions signals to callers that the data should not be modified.
- Named constants: Groups of related constants can be organized in frozendict structures for better code organization.
The implementation is efficient, with frozendict instances requiring only slightly more memory than regular dictionaries and offering comparable access performance. The hash is computed lazily and cached, so repeated hashing of the same frozendict does not incur additional cost.
Pattern Matching Enhancements
Python 3.10 introduced structural pattern matching, and each subsequent release has refined and expanded the feature. Python 3.15 continues this trend with several significant enhancements that make pattern matching more powerful and more practical for real-world use.
The most notable improvement is the addition of named capture groups in match statements. Previously, pattern matching in sequences required developers to specify the exact structure of the data being matched. With named capture groups, developers can extract specific elements by name, making patterns more readable and resilient to structural changes in the data.
Python 3.15 also introduces guarded pattern alternatives, allowing developers to specify multiple alternatives within a single case block, each with its own guard condition. This reduces code duplication in situations where multiple patterns should trigger the same handler but with different conditions.
Class pattern matching has been enhanced with support for keyword-only pattern arguments, making it easier to match against dataclasses and other classes with many attributes without having to specify every attribute in order.
The Python core team has also improved pattern matching performance significantly. The match statement compiler now generates optimized bytecode that can be up to three times faster than equivalent if-elif chains, making pattern matching the recommended approach for complex conditional logic.
Improved Error Messages and Debugging
Python has been steadily improving its error messages over the past several releases, and Python 3.15 continues this trend with some of the most helpful error messages the language has ever produced. The improvements are particularly notable in several areas:
Type errors now include more specific information about what went wrong. Instead of the generic “unsupported operand type(s)” message, Python 3.15 provides context about the expected types and the actual types provided. For example, attempting to concatenate a string and an integer now produces: “TypeError: Cannot concatenate ‘str’ and ‘int’: did you mean str(5) or int(’42’)?”
Import errors have been enhanced with suggestions based on common patterns. If you try to import a name that does not exist in a module, Python will suggest similar names from the module. If you try to import from a module that does not exist, Python will check for common misspellings and suggest corrections.
Attribute errors on common types now include contextual hints. Accessing .length on a string suggests using len(). Calling .keys() on a list suggests using enumerate(). These hints are generated by analyzing the most common mistakes found in public Python code repositories, making them genuinely useful for developers of all skill levels.
The 3.15 Standard Library Improvements
Beyond the headline features, Python 3.15 includes numerous improvements across the standard library. Some of the most noteworthy include:
- asyncio.TaskGroup enhancements: Task groups now support structured concurrency with improved cancellation semantics and better error reporting.
- Enhanced pathlib: The pathlib module gains new methods for working with symbolic links and improved cross-platform path handling.
- Improved dataclasses: Dataclasses now support computed fields, where a field’s value is derived from other fields, and improved integration with pattern matching.
- New itertools functions: Several new functions have been added to itertools, including
iter_windowfor sliding window operations anditer_chunksfor batch processing. - Updated typing module: The typing module receives support for variadic generics in a more ergonomic form, making it easier to define generic types with variable numbers of type parameters.
Performance Improvements Across the Board
Every Python release includes performance improvements, but Python 3.15 delivers some of the most significant speedups in recent memory. The continuing work on the CPython bytecode compiler, combined with optimizations in the interpreter loop, has yielded improvements across a wide range of workloads.
The most impactful optimization is in the function call machinery. Python 3.15 introduces a new calling convention that reduces the overhead of function calls by approximately 15 percent. For applications that make frequent function calls, particularly in hot loops, this translates directly into faster execution.
List and dictionary comprehensions have been optimized by approximately 20 percent through improved bytecode generation. Set operations see similar improvements. Integer arithmetic has been accelerated through better use of processor-level optimizations.
The cumulative effect of these optimizations is that Python 3.15 runs typical application code between 10 and 30 percent faster than Python 3.13, depending on the workload. For CPU-intensive tasks like data processing and numerical computation, the improvements are at the higher end of that range.
Preparing for the Upgrade
Upgrading to Python 3.15 requires careful planning, but the transition is generally smooth for codebases that are already on Python 3.12 or later. The Python Software Foundation has maintained its commitment to backward compatibility, and the vast majority of code written for Python 3.12 and 3.13 runs without modification on 3.15.
There are, however, a few deprecations and removals that developers should be aware of:
- The
distutilsmodule has been fully removed after being deprecated since Python 3.10. Projects still using distutils must migrate to setuptools or another build backend. - The
impmodule has been removed in favor ofimportlib. - Several deprecated
unittestmethods have been removed. - The
cgiandcgitbmodules have been removed, reflecting the declining use of CGI for web applications.
The Python Software Foundation recommends the following upgrade process:
- Ensure your test suite has comprehensive coverage of your codebase
- Run your tests with Python 3.15 in a CI environment before deploying
- Check for deprecation warnings in your current Python version, as these indicate code that may break in 3.15
- Update your pyproject.toml to specify the Python version constraint
- Verify that all third-party dependencies support Python 3.15
- Roll out the upgrade incrementally, monitoring for issues at each stage
Looking Ahead
Python 3.15 is a landmark release that demonstrates the language’s continued vitality and its ability to evolve while maintaining the stability and backward compatibility that have made it one of the world’s most popular programming languages. The introduction of lazy imports and frozendict addresses two of the most persistent pain points in the Python ecosystem, while the ongoing performance improvements ensure that Python remains competitive for an ever-widening range of applications.
The Python Steering Council has already begun discussing the roadmap for Python 3.16 and beyond. Early proposals include a more comprehensive type system overhaul, improved support for mobile and WebAssembly targets, and continued performance work aimed at making Python competitive with compiled languages for an even broader set of workloads. The foundation laid by Python 3.15’s innovations will support these future developments, ensuring that Python remains at the forefront of programming language innovation for years to come.
