Najbardziej wyczekiwane funkcjonalności w python 3.15
Co nowego słychać w Pythonie 3.15?
Wstęp
Niewątpliwie najnowsza wersja Pythona zbliża się do nas wielkimi krokami — zaplanowana na 1 października wersja 3.15 przynosi kilka ciekawych zmian. Poniżej znajdziecie moje opracowanie: rzeczy, które uznałem za najważniejsze. W momencie pisania tego artykułu dostępny jest już drugi release candidate do testowania zmian.
Spis treści
GIL
To chyba rzecz, którą każdy programista Pythona dobrze zna: Pythonowy mechanizm „Global Interpreter Lock” blokuje możliwość uruchamiania kodu Pythona w wielu wątkach jednocześnie. Sam doskonale pamiętam tę niezaprzeczalną regułę, jako że była ona jednym z pytań, jakie dostałem na obronie pracy magisterskiej.
Do wersji 3.12 włącznie powyższe stwierdzenie było prawdziwe. Teraz jednak twórcy Pythona rezygnują z tego mechanizmu — począwszy od wersji 3.13, wraz z kolejnymi wydaniami wprowadzane są nowe warianty interpretera, zwane free-threaded, które likwidują ten narzut i pozwalają swobodnie korzystać z wątków.
Mechanizm ten nie jest zwykłym przełącznikiem typu: odblokuj_pełną_wydajność_wątków = true, a raczej pełnoprawną migracją oprogramowania — ale to już materiał na osobny artykuł.
W wersji 3.15 ten mechanizm jest wciąż ulepszany — najważniejszą zmianą jest wprowadzenie nowego API języka C o nazwie abi3t (jako następca abi3), obsługującego wielowątkowość. Poza tym zwiększono hermetyzację (m.in. PyObject nie jest już bezpośrednio częścią struktury instancji w C), przygotowano oficjalne instalatory z obsługą free-threading oraz wprowadzono frozendict, o którym będzie dalej.
Sentinel
PEP-661, wchodzący wraz z Pythonem 3.15, wprowadza nową, tzw. wartość sentynel (Sentinel Value). Najprostszym przykładem wartości sentynel, już obecnym w Pythonie, jest None — nowy mechanizm ma umożliwiać tworzenie własnych wartości „pustych”, wykorzystywanych w przypadkach, gdy potrzebujemy dwóch różnych wartości pustych oznaczających dwie różne rzeczy. Wartości sentynel mają przy tym dużo przyjaźniejszy wygląd w logach. Ogólnie jest to miłe usprawnienie — więcej na ten temat można obejrzeć w ciekawym materiale przygotowanym przez BugBytes.
Lazy Imports
PEP-810 to chyba jedna z lepszych, wymiernych zmian w najnowszym wydaniu. Nie znam programisty Pythona, który nigdy nie miał cyklu w importach — ta zmiana może ułatwić unikanie takich cykli, sprawiając, że moduły będą ładowane dopiero, kiedy są potrzebne. Będzie to wymagało dostosowania kodu, ale myślę, że warto. Poza tym przyspieszą testy oraz cold start aplikacji webowych i narzędzi CLI — choć to drobiazgi w porównaniu do głównej zalety, a zresztą od czasu budowania (build time) jest devops, prawda?

Wracając na poważnie do tematu — wiąże się to z kilkoma pułapkami, które będą wymagały przebudowy kodu. Jedną z nich jest to, że użycie jednej funkcji z leniwie importowanego modułu oznacza zaimportowanie całego modułu, więc jeśli ktoś jeszcze tego nie robi, warto przełączyć się na importowanie tylko potrzebnych elementów.
Jeśli ktoś jeszcze nie orientuje się w temacie, polecam materiał na kanale anthony writes code jako uzupełnienie do PEP. Fun fact: Anthony był jedną z osób, które pracowały nad poprawą treści wyjątku dotyczącego cyklicznego importu.
Statyczne Typowanie
PEP-814: frozendict
Raczej nikogo nie zdziwi fakt, że frozendict to w praktyce dict (choć po nim nie dziedziczy!), tylko niezmienny — odpowiednik wdrożonego już wcześniej frozenset. Osobiście nie widzę jakichś wielkich zalet tego nowego typu, poza likwidacją kilku nieładnych hacków i możliwością cachowania funkcji przyjmującej słownik.
Kilka fajniejszych przykładów zastosowań dla frozendict, jakie udało mi się znaleźć:
# Możliwość zastosowania frozendict jako klucza, w tym przypadku do złożonego RBAC
user_permissions = {
frozendict(role="admin", region="US"): ["read", "write", "delete"],
frozendict(role="viewer", region="EU"): ["read"],
}# Przykład funkcji przyjmującej słownik, którą w 3.15 można cachować
from functools import cache
@cache
def get_expensive_report(report_config: frozendict):
return query_database(report_config)PEP-728: TypedDict
Krok w kierunku lepszego statycznego typowania — zmiana pozwala tworzyć własne typy słowników, zamknięte bądź z ograniczoną modyfikacją. Najlepiej zobrazować to na przykładzie:
from typing import TypedDict
# Słownik całkowicie ZAMKNIĘTY (żadnych dodatkowych kluczy!)
class StrictMovie(TypedDict, closed=True):
title: str
year: int
# Słownik, który pozwala na dodatkowe klucze, ale TYLKO typu str (np. tagi)
class FlexibleMovie(TypedDict, extra_items=str):
title: str
year: int
# --- JAK TO WYGLĄDA W PRAKTYCE? ---
# BŁĄD dla StrictMovie: "director" nie jest zdefiniowany, a słownik jest zamknięty
m1: StrictMovie = {"title": "Inception", "year": 2010, "director": "Nolan"}
# OK dla FlexibleMovie: "director" to str, więc extra_items=str to akceptuje
m2: FlexibleMovie = {"title": "Inception", "year": 2010, "director": "Nolan"}
# BŁĄD dla FlexibleMovie: "rating" to int (9), a dodatkowe pola mogą być tylko str!
m3: FlexibleMovie = {"title": "Inception", "year": 2010, "rating": 9}PEP-747 - TypeForm
To raczej funkcjonalność, która będzie bliższa twórcom bibliotek w Pythonie — nowa adnotacja TypeForm umożliwia funkcjom przyjmowanie adnotacji typu jako wartości:
from typing import TypeForm
# Funkcja przyjmuje dynamiczną formę typu (np. list[int])
def trycast[T](target_type: TypeForm[T], value: object) -> T | None:
# Uwaga: w praktyce potrzebna jest bardziej złożona logika niż zwykłe
# isinstance() — samo isinstance nie obsłuży np. sparametryzowanych
# generyków jak list[int]; stąd biblioteki takie jak `trycast`
# implementują własną, dopasowaną logikę sprawdzania w runtime.
if is_assignable(value, target_type):
return value
return None
# --- UŻYCIE ---
# Type checker (np. pyright) teraz IDEALNIE wie, że wynikiem będzie list[int] | None
result = trycast(list[int], [1, 2, 3])PEP-800 - @disjoint_base
Tę zmianę można chyba bardziej zakwalifikować jako bugfix do statycznego typowania. W dużym uproszczeniu: w wersji 3.14 można było stworzyć klasę-potwora, która była jednocześnie liczbą całkowitą i ciągiem znaków:
class MyAbomination(int, str): pass
W wersji 3.15 — już nie można.
Drobniejsze zmiany
PEP-798 - Ładniejsze rozpakowywanie list
Zamiast korzystać z itertools.chain lub functools.reduce, w Pythonie 3.15 będzie można użyć poniższego zapisu do rozpakowania listy list do jednej listy:
[*it for it in its] # lista złożona ze wszystkich elementów list z its
{*it for it in its} # set złożony ze wszystkich elementów list z its
{**d for d in dicts} # słownik będący kombinacją wszystkich słowników z dicts
(*it for it in its) # generator zwracający wszystkie elementy list z itsPozostałe
- PEP-799 - Nowy moduł profilera
profiling - PEP-782, PEP-788, PEP-803, PEP-820, PEP-793 — rozwój API C (w tym wspomniane wcześniej zmiany związane z GIL)
- Lepsze treści błędów
- PEP-831 - Udostępnienie observability na poziomie systemu w Pythonie
- PEP-686 - zmiana domyślnego kodowania na UTF-8
- PEP-829 - ograniczenie przestarzałego mechanizmu wykonywania kodu w plikach
.pthna rzecz jawnych punktów wejścia w nowych plikach.start
Na co osobiście najbardziej czekam
Osobiście najbardziej nie mogę się doczekać, aż pełnoprawna wielowątkowość zostanie dodana. Nie mogę wyzbyć się myśli o napisaniu prostego silnika gier komputerowych z wykorzystaniem Pythona — jak przystało na absolwenta GameDev'u — i choć nie będzie on raczej tak wydajny jak silniki pisane w C/C++, przewaga wynikająca z błyskawicznej pętli zwrotnej będzie ogromna.
Drugim featurem, na który czekam, jest lazy import — nienawidzę składni if TYPE_CHECKING: i staram się jej unikać, gdzie tylko mogę, bo nie jest ona zbyt atrakcyjna i czysta.
Podsumowanie
W Pythonie szykują się duże zmiany — wersje 3.13/3.14 były zalążkiem tego, natomiast 3.15 jest już wbiciem łopaty. Trzeba jednak jeszcze uzbroić się w cierpliwość, zanim te zmiany trafią do realnego, biznesowego użytku — może minąć kilka lat.
Źródła
- https://docs.python.org/3.15/whatsnew/3.15.html
- https://realpython.com/python315-frozendict/
- https://pydevtools.com/handbook/explanation/what-is-pep-703/
- https://blog.jetbrains.com/pycharm/2025/07/faster-python-unlocking-the-python-global-interpreter-lock/
- https://dotpy.pl/programowanie/python-gil/
- https://bulldogjob.pl/readme/python-dazy-do-usuniecia-gil-i-zwiekszenia-wspolbieznosci
- https://www.c-sharpcorner.com/article/python-3-15-free-threaded-builds-what-to-test-before-removing-the-gil
- https://www.youtube.com/watch?v=khIUGLwK-9U