Corso
Python ha vinto il premio TIOBE "Language of the Year" tre volte negli ultimi quattro anni. A metà del 2022, il popolare linguaggio di programmazione è ancora in cima all’indice TIOBE.

Python è in cima al TIOBE Index per giugno 2022.
Il punteggio del 12,74% per Python significa che la parola chiave “Python” è comparsa in circa il 13% di tutte le ricerche legate alla programmazione sui motori di ricerca. In altre parole, ogni 7ª o 8ª domanda di programmazione su Internet riguarda Python.
Oggi Python è spesso associato alle maggiori svolte nella data science e nell’IA. Chi lo usa apprezza la sua semplicità, l’ampio set di librerie integrate e di terze parti per una miriade di attività e la sua community.
Con l’arrivo della versione stabile finale della 3.11 il 3 ottobre 2022, la community Python può aspettarsi diversi aggiornamenti benvenuti, tra cui un notevole aumento di velocità, una gestione e un debug degli errori migliori e numerosi miglioramenti “di qualità della vita” alle librerie standard.
Quali sono le nuove funzioni di Python 3.11?
Il changelog di Python 3.11 è composto da un elenco quasi infinito di correzioni di bug, miglioramenti e aggiunte, molte delle quali potresti non notare mai. Tuttavia, alcune novità cruciali potrebbero migliorare sensibilmente il tuo flusso di lavoro in Python quando uscirà la release stabile. Le principali novità sono descritte qui sotto.
1. Miglioramento delle prestazioni
Il primo cambiamento significativo che entusiasmerà i data scientist è il miglioramento delle prestazioni: la suite di benchmark standard gira circa il 25% più veloce rispetto alla 3.10. La documentazione di Python afferma che la 3.11 può essere fino al 60% più veloce in alcuni casi. Ecco come eseguire tu stesso il benchmark per verificare i miglioramenti.
Per confrontare le velocità di Python 3.10 e 3.11, ti servirà un’installazione di Docker. Dopo esserti assicurato che Docker Desktop sia in esecuzione, lancia questi due comandi nel terminale, che scaricheranno due immagini per le due versioni di Python.
$ docker run -d python:3.10.4-bullseye
$ docker run -d python:3.11-rc-bullseye
Le immagini scaricate dovrebbero essere visibili nella dashboard di Docker, dove puoi anche avviarle.

Dopo aver avviato i container, puoi eseguire docker ps per vedere dettagli come l’ID del container, l’immagine, lo stato e altro.

I container in esecuzione sono visibili anche nella scheda Containers della dashboard, dove troverai i pulsanti CLI accanto a "Run/Stop". Cliccali per entrambi i container per aprire due tab del terminale collegati ai container.
In entrambe le tab, installa il pacchetto pyperformance tramite pip. Poi, esegui i comandi qui sotto in ciascun terminale del container.
Per Python 3.10
$ pyperformance run -r -o py310.json
Per Python 3.11
$ pyperformance run -r -o py310.jsone run -r -o py311.json
Questi comandi eseguiranno benchmark per circa 60 funzioni Python dalle librerie standard e produrranno metadati in JSON. Ho aggiunto opzionalmente il flag -r per indicare a pyperformance che voglio test più rigorosi, che richiedono più tempo. Nel mio caso, entrambi i container girano su una macchina Windows con CPU AMD Ryzen a 12 core.
Ora abbiamo due file JSON in due container, e dobbiamo metterli entrambi in una singola directory per continuare il test di velocità. Per farlo, useremo il comando docker cp per copiare il file JSON dal container Python 3.11 a quello 3.10.
Esegui i comandi qui sotto in una scheda del terminale separata, aggiornando il percorso con quello corretto sul tuo computer:
$ docker cp crazy_snyder:/py311.json C:/Users/bex/Desktop
$ docker cp C:/Users/bex/Desktop/py311.json charming_proskuriakova:/
Nel mio caso, crazy_snyder e charming_proskuriakova sono gli ID dei container in esecuzione. Ora entrambi i file JSON si trovano nel container di Python 3.10. Per completare il benchmark e salvare i risultati in CSV, esegui i comandi seguenti.
$ pyperformance compare py310.json py311.json --csv comparison.csv
$ docker cp charming_proskuriakova:/comparison.csv C:/Users/bex/Desktop/articles
Infine, carichiamo il CSV in Pandas e analizziamolo:
import matplotlib.pyplot as plt
import pandas as pd
import seaborn as sns
df = pd.read_csv("data/comparison.csv")
df.rename({"Base": "py310", "Changed": "py311", "Benchmark": "function"},
axis=1,
inplace=True)
>>> df.shape
(59, 3)
>>> df.sample(5)
| Function | py310 | py311 | |
| 40 | scimark_monte_carlo |
0.12392 | 0.08052 |
| 19 | mako |
0.01967 | 0.01289 |
| 1 | chameleon |
0.01131 | 0.00843 |
| 8 | float |
0.14265 | 0.09133 |
| 35 | regex_effbot |
0.00315 | 0.00261 |
Ogni riga del dataframe registra quanto tempo (in millisecondi) hanno impiegato entrambe le versioni a eseguire la funzione Python indicata. Tracciamo un istogramma degli speedup.
df["speed_up"] = (df["py310"] / df["py311"]).round(3)
fig, ax = plt.subplots()
sns.histplot(df["speed_up"], bins=11)
ax.set(xlabel="Speedup (times)",
title="Python 3.10 vs. Python 3.11 - speedup comparison")
plt.show()

Dal grafico è chiaro che la maggior parte delle funzioni in Python 3.11 è risultata circa 1,2 volte (20%) più veloce. Possiamo confermare la stima con media e mediana.
>>> df["speed_up"].mean()
1.2605593220338982
>>> df["speed_up"].median()
1.207
Sembra esserci un caso in cui l’aumento di velocità ha superato il 60%.
>>> df["speed_up"].max()
1.899
Ci sono anche quattro casi in cui Python 3.10 è stato più veloce della nuova versione.
>>> (df["speed_up"] <= 1).sum()
4
Sebbene gli incrementi prestazionali siano evidenti sulla carta, l’impatto specifico varierà da persona a persona e la versione stabile finale di Python 3.11 deve ancora uscire. Il benchmark usa funzioni Python pure, che potresti non utilizzare spesso nel lavoro quotidiano, ma impostare un’installazione Docker per entrambe le versioni 3.10 e 3.11 ti permetterà di farti un’idea degli aumenti di velocità per i tuoi casi d’uso specifici.
2. Messaggi di errore migliori
Un’altra novità interessante di Python 3.11 sono i messaggi di errore più precisi, che individuano l’esatta posizione dell’errore. Invece di restituire un traceback di 100 righe che termina con un messaggio difficile da interpretare, Python 3.11 punta all’espressione esatta che ha causato l’errore.

Nell’esempio sopra, l’interprete Python punta alla x che ha bloccato lo script a causa del suo valore None. L’errore sarebbe stato ambiguo nelle versioni attuali di Python poiché ci sono due oggetti con l’attributo ‘x’. Tuttavia, la gestione degli errori della 3.11 indica chiaramente l’espressione problematica.
Questo esempio mostra come la 3.11 individui un errore da un dizionario profondamente annidato e possa mostrare chiaramente a quale chiave appartiene l’errore.

La stessa precisione si vede nelle espressioni aritmetiche complesse.

I messaggi di errore aggiornati sono sicuramente un’aggiunta gradita, soprattutto per i data scientist che faticano a fare debug degli script.
3. Note sulle eccezioni
“Esplicito è meglio di implicito.”
La frase sopra è la seconda riga de Lo Zen di Python, un elenco dei 20 principi di progettazione di Python. Rappresenta la regola secondo cui il codice Python dovrebbe essere il più espressivo possibile.
Per rafforzare questo pattern di progettazione, Python 3.11 introduce le note sulle eccezioni (PEP 678). Ora, all’interno delle clausole except, puoi chiamare la funzione add_note() e passare un messaggio personalizzato quando generi un errore.
import math
try:
math.sqrt(-1)
except ValueError as e:
e.add_note("Negative value passed! Please try again.")
raise

Se hai scritto una classe di eccezione personalizzata come sotto, puoi aggiungere più note alla classe dentro l’attributo protetto a livello di classe __notes__:
import math
class MyOwnError(Exception):
# Should be a list of strings
__notes__ = ["This is a custom error!"]
try:
math.sqrt(-1)
except:
raise MyOwnError

Ora, quando l’interprete incontra l’errore, mostrerà il messaggio personalizzato dopo il corpo del messaggio normale.
C’è anche una nuova sintassi per sollevare eccezioni con la clausola except*, da usare insieme alla nuova classe ExceptionGroup. La PEP 654 dice che puoi usarla per sollevare più errori non correlati simultaneamente in vari scenari. La maggior parte di queste situazioni è piuttosto di nicchia, come lavorare con web socket, programmare in concorrenza con asyncio, ecc., che raramente emergono nel lavoro quotidiano di data science, ma se ti interessa puoi approfondire la nuova sintassi qui.
4. Nuova funzione di typing: Self
I linguaggi a tipizzazione statica aiutano a rendere il tuo codice leggibile e facile da fare debug. Definire il tipo esatto di variabili, input e output delle funzioni può farti risparmiare ore di debug e rendere il tuo codice più comprensibile agli altri. Aggiungere annotazioni di tipo permette anche agli IDE moderni di mostrare le definizioni delle funzioni mentre ne digiti i nomi, rendendo le tue funzioni più facili da interpretare per gli altri.
Finora, il potente modulo typing in Python aveva classi per praticamente qualsiasi tipo di dato, tranne per le classi che restituiscono istanze di se stesse. Nelle versioni attuali di Python, l’esempio sotto non è possibile:
from typing import Self
class Language:
def __init__(self, name, version, release_date):
self.name = name
self.version = version
self.release_date = release_date
def change_version(self, version) -> Self:
self.version = version
return Language(self.name, self.version, self.release_date)

Tuttavia, Python 3.11 introduce la classe Self, che puoi aggiungere alla definizione di una funzione se il suo valore di ritorno è self o una nuova istanza della classe stessa.
5. Miglioramenti alle librerie standard
Ci sono anche altri miglioramenti “di qualità della vita” alle librerie standard. Innanzitutto, sono state aggiunte due funzioni attese da tempo al modulo math.
>>> import math
>>> math.cbrt(9) # Find the cube-root of x
2.080083823051904
>>> math.cbrt(27)
3.0000000000000004
>>> math.exp2(5) # Raise 2 to the power of x
32.0
Il fatto che ci siano voluti 28 anni a Python per aggiungere la funzione della radice cubica è piuttosto sorprendente ma, come si suol dire, meglio tardi che mai.
Il modulo fractions ha aggiunto anche una nuova funzione che permette di creare frazioni da stringhe:
>>> from fractions import Fraction
>>> Fraction("22/7") # New in Python 3.11
Fraction(22, 7)
>>> Fraction(numerator=22, denominator=7)
Fraction(22, 7)
>>> Fraction("3e-4")
Fraction(3, 10000)
>>> Fraction("-73/41")
Fraction(-73, 41)
>>> Fraction(3.1415) # Find the closest approximation to the given float
Fraction(7074029114692207, 2251799813685248)
Come vedi, il modulo fractions può essere utile per certe operazioni aritmetiche. In particolare, è comodo trovare la frazione più vicina che approssima il float dato. Puoi spingerti oltre fornendo un limite al denominatore:
>>> from math import pi
>>> Fraction(pi).limit_denominator(100000)
Fraction(312689, 99532)
C’è anche un nuovo modulo chiamato tomllib per effettuare il parsing di documenti TOML. TOML (Tom's Obvious Minimal Language) è un formato di file popolare per scrivere file di configurazione leggibili dalle persone. Ecco un’immagine dalla homepage del progetto:

import tomllib
toml_str = """
python-version = "3.11.0"
release_date = "2022-10-22"
"""
data = tomllib.loads(toml_str)
print(data) # {'python-version': '3.11.0', 'release_date': '2022-10-22'}
print(data['python-version']) # 3.11.0
print(data['release_date']) # 2022-10-22
Data di rilascio di Python 3.11
Lo sviluppo di Python 3.11 è iniziato il 5 maggio 2021. Da allora sono state rilasciate sette versioni alpha, fino ad arrivare alla prima beta l’8 maggio 2022. Da questa data fino al rilascio ufficiale non verranno introdotte nuove funzioni in Python.
La release finale e stabile uscirà il 3 ottobre 2022, dopo altre tre versioni beta e due candidate nel frattempo. Puoi saperne di più su Python consultando le risorse qui di seguito.

