Skip to main content

Modern Python 2026: New Features, Tooling (uv, ruff), and What's Next

Modern Python 2026: New Features, Tooling (uv, ruff), and What's Next

🗓️  Jun 28, 2026

Post #1 opened with python3 --version and a single working program that converted miles to kilometers. Twenty posts later, that same program — the exact same core arithmetic, still — has functions, classes, persistent JSON history, a live currency API, a full test suite, retry logic, caching, concurrent network calls, profiling-confirmed performance fixes, and a proper Docker container. Nothing about what the program fundamentally does has changed since Post #1. Everything about how well it is built has.

This final post looks at where Python the language and its tooling ecosystem are heading — genuine, current features in Python 3.12 through 3.14, and ruff, the one significant tool this series has not yet covered, completing the modern toolchain alongside uv from Post #1. It closes with the unit converter’s complete journey, and a capstone exercise bringing every post’s lesson together one final time.


Python 3.12 and 3.13: What Actually Shipped

PEP 695: Cleaner Generic Type Syntax

Post #2 introduced type hints as the 2026 professional standard. Python 3.12 made generic (type-parameterized) functions and classes noticeably cleaner to write:

# The older way — still fully valid, seen in any codebase not yet updated
from typing import TypeVar
T = TypeVar("T")

def first_item(items: list[T]) -> T:
    return items[0]

# Python 3.12+ — the same thing, with dedicated syntax
def first_item[T](items: list[T]) -> T:
    return items[0]
class Stack[T]:
    def __init__(self) -> None:
        self._items: list[T] = []

    def push(self, item: T) -> None:
        self._items.append(item)

    def pop(self) -> T:
        return self._items.pop()

The [T] directly after the function or class name declares a type parameter without needing a separate TypeVar import and assignment — a small but genuinely welcome ergonomic improvement to exactly the type-hinting habit this series has built since Post #2.

Significantly Better Error Messages

Error messages have continued improving with each recent release — more precise pointers to exactly where in a line an error occurred, and more specific suggestions for common mistakes like typos in attribute or variable names:

>>> "hello".strp()
AttributeError: 'str' object has no attribute 'strp'. Did you mean: 'strip'?

This directly benefits every debugging session from Post #12 onward — clearer errors mean less time between “something failed” and “I understand why.”

Python 3.13: The Experimental Free-Threaded Build

This is the direct follow-up promised in Post #16’s concurrency discussion. Python 3.13 introduced an experimental build option that removes the GIL entirely — genuine multi-core parallelism for threads, without needing multiprocessing’s separate-process overhead for CPU-bound work.

# The free-threaded build is a separate installation option, not yet the default
# As of this series, it remains experimental and opt-in
python3.13t --version  # the "t" suffix denotes the free-threaded build

The honest state of this feature: it is real, it is genuinely significant, and it remains experimental — not every third-party package is fully compatible yet, and it is not the default CPython build most developers install. Post #16’s guidance (choose threading for I/O-bound work, multiprocessing for CPU-bound work, using the standard GIL-based interpreter) remains the correct approach for typical production code as of this series. Free-threaded Python is worth watching, not yet worth defaulting to for most projects.

Python 3.13’s Improved REPL

The interactive REPL from Post #1 has gotten meaningfully better — colorized output, proper multi-line editing (previously, editing a multi-line block you had already typed was awkward), and improved tab completion, making the REPL a genuinely more pleasant tool for the kind of quick experimentation this series has occasionally used it for.

Python 3.14: The Current Stable Release

As referenced back in Post #1, Python 3.14 is the current stable release as this series concludes, continuing the trajectory of the previous two versions — further performance work, continued refinement of the free-threading and JIT compilation efforts introduced experimentally in 3.13, and the usual annual cycle of smaller quality-of-life improvements. Every code example across this entire series runs identically on 3.13 and 3.14; nothing in the fundamentals this series taught is version-specific to one or the other.


ruff: Completing the Modern Toolchain

This series has used uv since Post #1 for package and project management, and pytest since Post #11 for testing. The piece that has not yet been introduced: linting and formatting — automatically checking code for style issues, potential bugs, and consistent formatting.

Historically, this required several separate tools working together: black for formatting, flake8 for linting, isort for import sorting, and often pylint for deeper static analysis — each with its own configuration, its own speed characteristics, and occasional friction between them. ruff, written in Rust by the same team behind uv, replaces essentially this entire toolchain with one dramatically faster tool.

uv add --dev ruff
# Check for issues
uv run ruff check .

# Automatically fix what can be fixed
uv run ruff check --fix .

# Format code (the black-equivalent functionality)
uv run ruff format .

Configuring ruff

# pyproject.toml — add alongside the [project] section from Post #19
[tool.ruff]
line-length = 100
target-version = "py313"

[tool.ruff.lint]
select = [
    "E",   # pycodestyle errors
    "F",   # pyflakes (undefined names, unused imports)
    "I",   # import sorting
    "UP",  # pyupgrade (suggests modern syntax over outdated patterns)
]

Running ruff check . against any code from earlier in this series would catch things like unused imports left over from refactoring, inconsistent import ordering, and — via the UP rule set — flag outdated patterns in favor of the modern equivalents this series has taught throughout (for instance, suggesting the newer X | None union syntax over the older Optional[X] from the typing module, wherever it finds the older form).

The Complete Modern Toolchain, Assembled

Every tool this series has actually used, presented together as the current standard stack:

Tool Purpose Introduced
uv Package, environment, and project management Post #1
ruff Linting and formatting This post
pytest Testing Post #11
pyright / mypy Static type checking Post #2
Docker Containerized deployment Post #19
# The complete quality check, run before considering any change finished
uv run ruff check --fix .
uv run ruff format .
uv run pytest

This three-command sequence — lint and auto-fix, format consistently, run the test suite — is close to the actual daily workflow of a professional Python developer in 2026, and every piece of it was covered somewhere across this series, even though it was never assembled into one sequence until this final post.


Looking Forward: What’s Worth Watching

Offered with appropriate hedging, since none of this is guaranteed to land on any specific timeline:

Free-threading maturing toward default status: As third-party package compatibility improves, the free-threaded build introduced experimentally in 3.13 is a reasonable candidate to eventually become more broadly adopted — worth revisiting Post #16’s guidance periodically as this matures, rather than treating it as settled permanently.

Continued JIT compiler development: Performance work on CPython’s execution speed continues incrementally each release — nothing that changes the fundamentals this series taught, but a trend worth being generally aware of if raw execution speed becomes a genuine concern for a specific project (exactly the kind of concern Post #17 taught you to actually measure rather than assume).

The type system continuing to mature: PEP 695’s cleaner generic syntax is part of a broader, ongoing trend of Python’s type hinting system becoming more expressive and more ergonomic — the fundamentals from Post #2 remain stable; the specific syntax for advanced cases continues to improve.

Tooling consolidation continuing: uv and ruff, both from the same team, represent a broader trend toward faster, more unified tooling replacing what used to require several separate, slower tools — a trend likely to continue in some form, even if the specific tools change over a longer horizon.


The Unit Converter’s Complete Journey

A direct retrospective, post by post, on the single program that ran throughout this entire series:

Post #1: A single file, if/elif branches, input() and print() — functional, but fragile and repetitive.

Post #3: Wrapped in while True:, runs repeatedly instead of exiting after one conversion.

Post #4: Each conversion extracted into its own tested-independently function.

Post #5: The entire elif chain replaced by a four-line dictionary — later recognized, in Post #18, as the Strategy pattern.

Post #6: A UnitConverter class, adding genuine state — conversion history — that the earlier function-only version had no way to hold.

Post #7: Proper exception handling — the long-standing crash on invalid input, finally fixed.

Post #8: Split across multiple files and modules, no longer one growing script.

Post #9: History persisted to JSON, surviving the program closing and reopening.

Post #10: A genuinely live feature — real-time currency conversion — that no hardcoded constant could replicate.

Post #11: A real test suite, including mocked tests for the network-dependent currency feature.

Post #13: A hand-built caching closure for exchange rate lookups.

Post #14: The manual cache replaced by functools.lru_cache; retry logic added as a reusable @retry decorator.

Post #16: Multiple currency lookups running concurrently instead of one at a time.

Post #17: Actually profiled, with a genuine, measured performance bug found and fixed elsewhere in the series’ example code.

Post #18: The dictionary dispatch explicitly recognized as the Strategy pattern; an Observer-based extension sketched for logging and history-saving.

Post #19: Properly packaged, configured via environment variables, containerized with Docker.

The arithmetic converting miles to kilometers has not changed once since Post #1. Everything around it — how it is organized, tested, secured, deployed, and understood — reflects every lesson this series has covered.


Final Capstone Exercise

Bring every post’s lesson together one more time. Add a new conversion category to the unit converter — weight/mass (pounds to kilograms, for instance) — implementing it completely:

  1. Add the conversion function to conversions.py with proper type hints (Post #2, #4)
  2. Register it in the CONVERSIONS dictionary (Post #5, recognized as Strategy in Post #18)
  3. Write parametrized tests covering it, including edge cases (Post #11)
  4. Add appropriate error handling for invalid input (Post #7)
  5. If you added any new configurable value, wire it through Config (Post #19)
  6. Run ruff check --fix . and ruff format . and confirm everything passes cleanly
  7. Run the full test suite and confirm it passes

Completing this exercise end to end is the most direct possible confirmation that the twenty posts’ worth of individual lessons have genuinely combined into one coherent, professional workflow, rather than twenty disconnected facts.


FAQ

Q: Should I switch to the free-threaded Python build right now? A: For most projects, no — it remains experimental, and package compatibility is still maturing. Post #16’s standard guidance (threading for I/O-bound, multiprocessing for CPU-bound) remains the right default for production code as this series concludes.

Q: Do I need to migrate existing projects to use ruff immediately? A: Not urgently, but it is a low-risk, high-value change — ruff is a drop-in replacement for slower, older tooling in the overwhelming majority of cases, and the speed difference alone (frequently 10-100x faster than the tools it replaces) is noticeable on any codebase of meaningful size.

Q: Is this series’ knowledge going to become outdated quickly? A: The fundamentals — data types, control flow, functions, OOP, error handling, testing, the standard library patterns — are stable and have been for years; they will remain the foundation regardless of which specific tooling is fashionable in a given year. The tooling layer (uv, ruff, specific library versions) is the part most likely to continue evolving, which is exactly why this series emphasized understanding the underlying concepts, not just memorizing today’s specific commands.

Q: What should I actually build next, now that the series is complete? A: Take a real problem from your own work or life — something you would genuinely use — and build it using this series’ full toolkit: proper structure from the start (Post #8), tests as you go (Post #11), type hints throughout (Post #2), and packaging it properly (Post #19) once it works. The unit converter’s twenty-post evolution is the template; apply it to something of your own.


Series Conclusion

Twenty posts ago, this series opened by pointing out that most “Getting Started with Python” guides teach a print statement and call it complete. This series did something different: one program, evolved deliberately and honestly across every post, with each new concept applied to real, accumulating code rather than a disconnected new example every time. The unit converter is not particularly special as a program — its value was always in being a consistent, honest testbed for every lesson to land on concretely, from Post #1’s basic input() handling through Post #19’s Docker container.

If you have completed the exercises across this series — not just read them, but written and run the code — you now have genuine, demonstrated Python proficiency: the language fundamentals, the standard library’s most-used tools, testing discipline, debugging process, performance measurement habits, design pattern vocabulary, and the modern tooling ecosystem, all connected to real, working code you built yourself rather than abstract examples you only read about.

Your next step: Complete the final capstone exercise above. Then close this tab, open a genuinely new project — something you actually want to build — and start it the way Post #1 started: uv init, and a first real program, not a print statement.


Last updated: June 2026. Code tested with Python 3.13 and 3.14. Python’s tooling ecosystem evolves continuously — verify current recommended versions at python.org, docs.astral.sh/uv, and docs.astral.sh/ruff.

Share This Post

Enjoyed this article?

Get notified when we publish new guides and tutorials. No spam, unsubscribe anytime.

📬 Newsletter coming soon — stay tuned!

The information contained on this blog is for academic and educational purposes only. Unauthorized use and/or duplication of this material without express and written permission from this site’s author and/or owner is strictly prohibited. The materials (images, logos, content) contained in this web site are protected by applicable copyright and trademark law.