Ogni linguaggio ha quelle funzionalità che scopri un anno dopo che ti servivano, e la reazione è sempre la stessa: “aspetta, c’era da tutto questo tempo?” Ecco la mia lista per Python.
Dataclass (3.7+)
Prima di conoscere le dataclass, scrivevo __init__, __repr__ e __eq__ a mano per ogni contenitore di dati. Una perdita di tempo totale.
from dataclasses import dataclass
@dataclass
class Trade:
symbol: str
price: float
quantity: int
side: str = "buy" # valore predefinito
Tutto qui. Ottieni __init__, __repr__, __eq__ e ordinamento opzionale gratis. Aggiungi frozen=True se vuoi l’immutabilità. La classe qui sopra sostituisce circa 20 righe di boilerplate.
Se stai ancora scrivendo classi con nient’altro che self.x = x dentro __init__, fermati e usa una dataclass. Il te stesso del futuro ti ringrazierà.
Il walrus operator := (3.8+)
Assegna un valore e usalo nella stessa espressione. Sembra banale, ma elimina un tipo specifico di duplicazione fastidiosa:
# Prima: calcola, poi controlla
line = f.readline()
while line:
process(line)
line = f.readline()
# Dopo: assegna e controlla in un solo passaggio
while (line := f.readline()):
process(line)
Ottimo anche nelle list comprehension con chiamate costose:
results = [
clean
for raw in data
if (clean := expensive_transform(raw)) is not None
]
Non abusarne. Se l’espressione è già semplice, := aggiunge solo rumore. Ma quando evita di chiamare la stessa funzione due volte, è perfetto.
Debug con f-string (3.8+)
Aggiungi = dopo un’espressione in una f-string e Python stampa sia l’espressione che il suo valore:
x = 42
print(f"{x = }") # stampa: x = 42
print(f"{x * 2 = }") # stampa: x * 2 = 84
print(f"{len(data) = }") # stampa: len(data) = 1500
Prima scrivevo print(f"x: {x}") ovunque. Il trucco con = è più veloce, meno soggetto a errori e si auto-documenta. Non tornerai più indietro.
I match statement (3.10+)
La versione Python del pattern matching. Se hai usato match in Rust o Scala, ti sembrerà familiare (anche se meno potente). Se finora hai incatenato blocchi if/elif, ecco l’upgrade:
match command.split():
case ["quit"]:
exit_game()
case ["move", direction]:
player.move(direction)
case ["pick", "up", item]:
player.pick_up(item)
case _:
print("Unknown command")
La vera potenza è il matching strutturale — puoi destrutturare dizionari, oggetti e strutture annidate:
match event:
case {"type": "click", "x": x, "y": y}:
handle_click(x, y)
case {"type": "scroll", "delta": d} if d > 0:
scroll_up(d)
case {"type": "scroll", "delta": d}:
scroll_down(abs(d))
È strettamente necessario? No. if/elif funziona ancora. Ma match rende leggibile la logica di dispatch complessa, e la leggibilità è tutto.
Type hint che aiutano davvero (3.9+)
Non hai più bisogno di from typing import List, Dict, Optional. I generici integrati funzionano direttamente:
# Vecchio modo
from typing import List, Dict, Optional
def process(items: List[Dict[str, Optional[int]]]) -> None: ...
# Modo moderno (3.9+)
def process(items: list[dict[str, int | None]]) -> None: ...
La sintassi X | Y per i tipi union (3.10+) elimina Optional e Union in un colpo solo. Combinala con mypy o pyright e catturi i bug prima che arrivino in produzione. Non a runtime — Python non verifica i tipi a runtime — ma nel tuo editor e nella pipeline CI.
Merge di dict | dict (3.9+)
defaults = {"color": "blue", "size": "medium"}
overrides = {"size": "large", "shape": "circle"}
merged = defaults | overrides
# {'color': 'blue', 'size': 'large', 'shape': 'circle'}
Niente più {**a, **b}. L’operatore | è più pulito e puoi usare |= per il merge in-place. Una piccola cosa, ma salta fuori continuamente nella gestione delle configurazioni.
itertools.batched() (3.12+)
Quante volte hai scritto un ciclo per dividere una lista in gruppi di N? Troppe. Adesso è integrato:
from itertools import batched
list(batched("ABCDEFG", 3))
# [('A', 'B', 'C'), ('D', 'E', 'F'), ('G',)]
Se sei bloccato su un Python più vecchio, more-itertools ha chunked() che fa la stessa cosa.
Il pattern
La maggior parte di queste funzionalità condivide un tratto: riducono il boilerplate. Python è sempre stato bravo a dire quello che intendi in meno righe, e ogni release porta questo concetto un passo avanti. Il costo di non aggiornarsi non è che il tuo codice si rompe — è che scrivi più codice del necessario, e più codice significa più bug.
Scegli una funzionalità da questa lista che non hai ancora usato, provala nella tua prossima PR, e guarda quanto velocemente diventa un’abitudine.