Python 3.15: cosa cambia davvero per chi programma
Scopri cosa cambia davvero con Python 3.15: lazy import, frozendict, sentinel, comprehension con unpacking, UTF-8 di default, profiling con Tachyon e novità sul free-threading.
Python 3.15 è ormai molto vicino alla release definitiva. Dopo mesi di sviluppo, la versione è arrivata alla fase Release Candidate e la pubblicazione finale è prevista per il 1° ottobre 2026.
Come accade a ogni nuova release di Python, l’elenco delle novità è piuttosto lungo. Ci sono nuove funzionalità, cambiamenti nella sintassi, miglioramenti alle prestazioni, interventi sulla standard library e modifiche che riguardano soprattutto chi sviluppa librerie o estensioni native.
Il punto interessante, però, non è semplicemente capire cosa è stato aggiunto.
La domanda più utile è un’altra: cosa cambia davvero per chi programma in Python?
Python 3.15 non introduce una rivoluzione paragonabile al passaggio da Python 2 a Python 3. Non cambia completamente il linguaggio e non obbliga gli sviluppatori a riscrivere le proprie applicazioni.
Quello che fa, invece, è intervenire su diversi aspetti molto concreti: il tempo necessario per avviare un programma, il modo in cui modelliamo alcuni tipi di dati, la leggibilità di alcune espressioni, la gestione dell’encoding, gli strumenti di profiling e il percorso verso un ecosistema Python sempre più compatibile con il free-threading.
Ed è proprio osservando questi cambiamenti nel loro insieme che si può capire la direzione che Python sta prendendo.
Python 3.15 RC2: perché è già il momento giusto per parlarne
Prima di entrare nelle singole novità bisogna chiarire un aspetto importante: Python 3.15 non è ancora, al momento della pubblicazione di questo articolo, la versione definitiva.
È arrivato però alla fase RC2, cioè alla seconda Release Candidate.
Durante lo sviluppo di una nuova versione di Python si attraversano diverse fasi. Inizialmente ci sono le versioni alpha, utilizzate per introdurre e sperimentare le nuove funzionalità. Successivamente arrivano le beta, che segnano sostanzialmente il congelamento delle feature. Da quel momento il lavoro si concentra soprattutto sulla correzione dei problemi.
Le Release Candidate sono ancora più vicine alla versione definitiva.
Questo significa che Python 3.15 RC2 non è ancora la versione che installerei senza esitazioni su un server di produzione, ma rappresenta già molto bene quello che diventerà Python 3.15 finale.
Per gli sviluppatori è quindi un momento particolarmente interessante. Possiamo iniziare a provare la nuova versione, aggiungerla eventualmente ai test automatici e verificare la compatibilità delle nostre applicazioni senza dover aspettare il giorno della release.
I lazy import cambiano il modo in cui possiamo pensare allo startup
Una delle novità più interessanti di Python 3.15 riguarda gli import.
Normalmente, quando scriviamo:
import pandas
Python deve individuare il modulo, caricarlo ed eseguire il codice necessario all’import nel momento in cui incontra quella istruzione.
Per un programma piccolo questo costo è spesso trascurabile.
La situazione cambia quando abbiamo un’applicazione più complessa. Pensiamo per esempio a un tool da riga di comando con decine di dipendenze. Potremmo lanciare quel programma soltanto per visualizzare l’help, ma durante lo startup Python potrebbe comunque dover caricare moduli che in quella particolare esecuzione non verranno mai utilizzati.
Con Python 3.15 arriva una nuova possibilità: i lazy import.
Possiamo indicare esplicitamente che un modulo non deve essere caricato immediatamente:
lazy import pandas
Il modulo verrà effettivamente caricato soltanto quando il nome verrà utilizzato per la prima volta.
La differenza è importante perché non si tratta di una modifica globale del comportamento degli import. Python non trasforma automaticamente ogni import in un caricamento lazy.
Il normale import continua a essere eager, cioè immediato.
È lo sviluppatore a scegliere quali dipendenze possono essere caricate in modo ritardato.
Questo approccio è particolarmente interessante per applicazioni grandi, command line tool e software con dependency tree complessi.
Naturalmente non significa che dovremmo iniziare a scrivere lazy import ovunque.
Gli import Python possono infatti avere side effect. Un modulo potrebbe registrare plugin, configurare qualcosa oppure eseguire codice di inizializzazione semplicemente nel momento in cui viene importato.
Rimandare l’import significa quindi anche rimandare quei comportamenti.
La vera novità, quindi, non è soltanto una possibile riduzione del tempo di startup. È il fatto che Python introduce un meccanismo esplicito per controllare meglio quando vengono caricate alcune dipendenze.
frozendict: finalmente un dizionario immutabile built-in
Un’altra novità interessante riguarda un tipo di struttura dati che mancava da tempo nella libreria standard.
Python possiede già versioni immutabili di alcune strutture molto comuni. Le tuple possono essere viste come l’alternativa immutabile alle liste, mentre frozenset rappresenta l’equivalente immutabile dei set.
Per i dizionari, però, mancava una soluzione built-in equivalente.
Python 3.15 introduce frozendict.
Possiamo scrivere, per esempio:
config = frozendict(
debug=False,
port=8000
)
Una volta creato, quel mapping non può più essere modificato.
Questo può sembrare un dettaglio, ma l’immutabilità è molto utile nella progettazione del software.
Pensiamo a una configurazione che deve essere letta da diversi componenti dell’applicazione ma che non vogliamo possa essere modificata accidentalmente. Utilizzare una struttura immutabile comunica direttamente questa intenzione.
Non stiamo semplicemente dicendo agli altri sviluppatori “non modificare questo dizionario”.
Stiamo costruendo l’oggetto in modo che quella modifica non sia consentita.
Inoltre, quando chiavi e valori sono hashable, anche il frozendict può diventare hashable. Questo apre scenari nei quali un normale dizionario non potrebbe essere utilizzato.
È una di quelle novità che probabilmente non cambieranno da sole il modo in cui programmiamo, ma che rendono il linguaggio più coerente e più espressivo.
Sentinel: un piccolo problema che finalmente ha una soluzione standard
Un altro caso interessante riguarda i valori sentinella.
Supponiamo di avere una funzione nella quale None rappresenta un valore perfettamente valido.
Dobbiamo però distinguere due situazioni differenti: l’utente ha passato esplicitamente None, oppure non ha fornito affatto il parametro?
Per molti anni uno dei pattern più utilizzati è stato questo:
MISSING = object()
A quel punto MISSING viene utilizzato come valore speciale per indicare l’assenza del parametro.
Il sistema funziona, ma è un pattern che ogni progetto finisce per implementare autonomamente.
Python 3.15 introduce invece un meccanismo dedicato:
MISSING = sentinel("MISSING")
Possiamo quindi utilizzare quel valore in modo molto più esplicito:
if value is MISSING:
...
La differenza può sembrare piccola, ma ancora una volta il punto interessante è la standardizzazione.
Python prende un pattern che esiste da anni nel codice reale e gli fornisce una rappresentazione nativa e più chiara.
È anche un miglioramento utile per il debugging, per la leggibilità delle signature e per il typing.
Le comprehension diventano più espressive con l’unpacking
Python 3.15 introduce anche una modifica sintattica che potrebbe diventare rapidamente visibile nel codice quotidiano.
Consideriamo una lista di liste:
groups = [[1, 2], [3, 4], [5]]
Se vogliamo ottenere una lista piatta, una classica list comprehension potrebbe essere:
[x for group in groups for x in group]
Il codice funziona perfettamente, ma bisogna leggerlo con attenzione per capire l’ordine dei due for.
Con Python 3.15 possiamo utilizzare direttamente l’unpacking nella comprehension:
[*group for group in groups]
Il significato diventa immediato: per ogni gruppo, prendine gli elementi e inseriscili nella nuova lista.
Lo stesso principio può essere applicato anche ad altre strutture.
Per esempio, possiamo combinare una serie di dizionari utilizzando:
{**d for d in dictionaries}
Non è una rivoluzione della sintassi Python, ma è un buon esempio di come il linguaggio continui a evolversi cercando di esprimere in modo più diretto operazioni già molto comuni.
UTF-8 diventa il comportamento predefinito
Un cambiamento meno evidente, ma probabilmente molto importante sul lungo periodo, riguarda la gestione dell’encoding.
Storicamente, un’istruzione come:
open("config.txt")
senza specificare esplicitamente l’encoding poteva produrre comportamenti differenti a seconda del sistema operativo e del locale configurato.
Un programma sviluppato e testato su Linux poteva quindi incontrare problemi una volta eseguito su un altro ambiente.
Con Python 3.15 UTF-8 diventa l’encoding predefinito.
Questo rende il comportamento più prevedibile e riduce una classe di problemi legati alle differenze tra piattaforme.
Non significa però che specificare l’encoding sia diventato inutile.
Quando conosciamo il formato dei dati, scrivere:
with open("data.txt", encoding="utf-8") as file:
...
continua a essere una buona pratica.
È particolarmente importante se il nostro codice deve funzionare anche con versioni precedenti di Python.
Il punto è un altro: il comportamento predefinito del linguaggio diventa più coerente con il modo in cui oggi vengono gestiti i file di testo nella maggior parte dei sistemi.
Python 3.15 investe molto anche nel profiling
Probabilmente una delle aree più interessanti di Python 3.15 riguarda gli strumenti per osservare cosa succede durante l’esecuzione di un programma.
La nuova versione introduce un namespace profiling e soprattutto un nuovo
statistical sampling profiler chiamato Tachyon.
Per capire perché è interessante bisogna distinguere due modi differenti di fare profiling.
Un profiler deterministico segue molto da vicino l’esecuzione del programma, registrando chiamate alle funzioni e tempi di esecuzione. Fornisce informazioni molto dettagliate, ma questa osservazione ha inevitabilmente un costo.
Un sampling profiler utilizza un approccio differente.
Invece di osservare continuamente ogni evento, controlla periodicamente dove si trova il programma durante l’esecuzione. Da questi campioni costruisce una rappresentazione statistica delle aree in cui viene trascorso più tempo.
Il vantaggio è che l’impatto sul programma può essere significativamente inferiore.
Tachyon può inoltre collegarsi a un processo Python già in esecuzione utilizzando il PID.
Questo è particolarmente interessante in scenari reali.
Immaginiamo un servizio che improvvisamente inizia a consumare molta CPU oppure mostra latenze difficili da spiegare.
Poter osservare il processo senza dover modificare il codice e riavviare l’applicazione rende il profiling molto più adatto anche al troubleshooting di sistemi reali.
Python 3.15 migliora inoltre l’integrazione con strumenti esterni grazie all’uso dei frame pointer sulle piattaforme che li supportano.
Questo facilita il lavoro di profiler di sistema, debugger e strumenti basati su tecnologie come eBPF.
È probabilmente una delle aree che raccontano meglio l’evoluzione moderna di Python: non soltanto nuove funzionalità nel linguaggio, ma strumenti migliori per capire come il software si comporta realmente.
Python 3.15 e free-threading: attenzione a non confondere le versioni
Quando si parla delle versioni recenti di Python è inevitabile arrivare al tema del GIL e del free-threading.
Qui è importante però evitare una semplificazione molto diffusa.
Python 3.15 non è la versione che improvvisamente elimina il GIL.
Il percorso è iniziato prima.
Python 3.13 ha introdotto le build free-threaded in forma sperimentale. Python 3.14 ha fatto un passo successivo rendendole ufficialmente supportate, anche se continuano a essere opzionali.
Python 3.15 interviene invece soprattutto sull’ecosistema.
Una delle novità più importanti è l’introduzione di abi3t, una Stable ABI
pensata per le build free-threaded.
Questo interessa principalmente chi sviluppa estensioni native Python.
L’obiettivo è rendere progressivamente più semplice distribuire librerie compatibili senza dover ricompilare continuamente versioni differenti per ogni release dell’interprete.
È un lavoro meno visibile rispetto a una nuova sintassi, ma è fondamentale se il free-threading deve diventare realmente utilizzabile su larga scala.
Cosa non cambia con Python 3.15
Proprio perché intorno alle nuove versioni di Python si genera spesso molto entusiasmo, è utile chiarire anche ciò che Python 3.15 non fa.
Gli import normali rimangono eager. I lazy import devono essere richiesti esplicitamente.
Il GIL non scompare automaticamente dalla build standard.
Il JIT di CPython continua a essere un progetto in evoluzione e rimane sperimentale. È stato migliorato, ma non avrebbe senso sintetizzare tutto dicendo che “Python 3.15 è molto più veloce” come se ogni applicazione ottenesse automaticamente lo stesso incremento prestazionale.
Non avviene nemmeno il passaggio a Python 3.26 che era stato proposto in passato attraverso un modello di calendar versioning.
Dopo Python 3.14 arriva normalmente Python 3.15.
Queste precisazioni sono importanti perché aiutano a separare l’evoluzione reale del linguaggio dall’hype che spesso accompagna le nuove release.
Conviene aggiornare subito a Python 3.15?
La risposta dipende soprattutto dal momento in cui stiamo leggendo questo articolo.
Finché Python 3.15 è ancora in fase RC, non lo utilizzerei come sostituto immediato della versione Python che gestisce un ambiente di produzione stabile.
Questo non significa però che bisogna ignorarlo.
Al contrario, è il momento ideale per testarlo.
Chi mantiene librerie Python dovrebbe iniziare a verificare compatibilità, test automatici e produzione delle wheel.
Anche chi sviluppa applicazioni può creare un ambiente virtuale separato oppure aggiungere Python 3.15 alla matrice della propria CI.
In questo modo possiamo scoprire eventuali incompatibilità prima che la nuova versione diventi definitiva.
Dopo la pubblicazione di Python 3.15 finale, prevista per il 1° ottobre 2026, il discorso cambia ma il principio rimane lo stesso.
Aggiornare immediatamente non è necessariamente un vantaggio.
La prima verifica da fare riguarda sempre le dipendenze.
Se il progetto utilizza molte librerie di terze parti, e soprattutto librerie con estensioni native, è importante controllare che tutto l’ecosistema utilizzato dichiari effettivamente il supporto per Python 3.15.
Una nuova versione dell’interprete non rende improvvisamente obsoleta la versione precedente.
Un aggiornamento ragionato, testato e pianificato è quasi sempre preferibile a una migrazione fatta semplicemente perché è disponibile una release più recente.
Python 3.15 è una rivoluzione?
Probabilmente no.
Python 3.15 non cambia radicalmente il modo in cui scriviamo programmi Python.
Ed è proprio questo il punto.
Python è ormai un linguaggio estremamente maturo, utilizzato in ambiti che vanno dallo scripting allo sviluppo web, dalla data science all’intelligenza artificiale, fino all’automazione e alle infrastrutture.
Una release moderna deve quindi riuscire a migliorare il linguaggio senza rompere milioni di progetti esistenti.
Python 3.15 sembra muoversi esattamente in questa direzione.
I lazy import consentono di controllare meglio il costo dello startup.
frozendict e sentinel rendono più chiari pattern che esistevano già da
tempo. Le comprehension diventano più espressive. UTF-8 come default migliora
la portabilità. I nuovi strumenti di profiling rendono più semplice osservare
applicazioni reali. Il lavoro sulla Stable ABI continua a preparare
l’ecosistema al futuro del free-threading.
Nessuna di queste novità, presa singolarmente, trasforma completamente Python.
Insieme, però, raccontano molto bene dove sta andando il linguaggio.
Meno lavoro inutile, maggiore prevedibilità, API più espressive, strumenti migliori per capire le prestazioni e un runtime che continua a evolversi senza perdere la compatibilità che ha contribuito al successo di Python.
Python 3.15 non è quindi una rivoluzione.
È un’evoluzione importante.
E per un linguaggio maturo, probabilmente, è esattamente ciò che ci si dovrebbe aspettare da una buona release.