Python, dalle fondamenta Lezione 23 / 60

Sviluppo Python AI-assisted: il workflow che funziona

Dove Copilot/Claude/Cursor aiutano davvero, dove fanno male, e i pattern di prompt che producono codice da spedire.

Nel 2026, gli assistenti AI sono parte normale del toolchain Python. Copilot è nell’editor, Claude è a un tasto di distanza in Cursor, Aider vive nel terminale, Codex ha la sua CLI, Continue si aggancia sia a VS Code che a JetBrains. Far finta che non esistano è una tassa sulla produttività. Trattarli come oracoli è una tassa sulla qualità. La domanda interessante è quella di mezzo: dove ripagano il tempo che spendi a fare prompt, e dove ti costano più di quanto ti facciano risparmiare.

Questa lezione è il workflow su cui mi sono assestato. Cinque categorie di aiuto che funzionano davvero, i modi di fallire che mordono, e i pattern di prompt che trasformano un assistente AI da macchina che indovina a junior pair competente.

Cinque categorie in cui gli assistenti AI si guadagnano lo stipendio

1. Generazione di boilerplate

Questa è regalata. Tutto quello che è strutturale, ripetitivo, e segue un template, l’AI lo fa quasi alla perfezione:

  • Uno scaffold CLI con click o typer da una descrizione di una riga.
  • Una @dataclass da un sample JSON.
  • Una tabella pytest.mark.parametrize da “ecco sei casi”.
  • Stub di test da una firma di funzione.
  • Uno scheletro di pyproject.toml.

Un esempio reale. Incolli un blob JSON e dici “dataclass per questo, frozen, con slots”:

from dataclasses import dataclass

@dataclass(frozen=True, slots=True)
class Customer:
    id: int
    name: str
    email: str
    signup_date: str
    is_active: bool

Sono trenta secondi risparmiati, venti volte al giorno. La categoria è ampia: ogni volta che stai scrivendo qualcosa di cui la tastiera già conosce la forma, un assistente AI lo scriverà più veloce di te e raramente lo sbaglierà.

2. Recupero di API

“Come uso resample di Pandas per fare bucket per settimana che inizia di lunedì?” Una volta voleva dire una scheda sui doc e tre minuti di scorrere. Ora è una domanda e una risposta con un blocco di codice funzionante:

import pandas as pd

weekly = df.resample("W-MON", on="ts", label="left").sum()

Per le librerie mainstream, pandas, numpy, requests, sqlalchemy, fastapi, polars, il recupero AI è più veloce e più mirato della consultazione dei doc. L’avvertenza: è tarato sul caso comune. Per API oscure, librerie di nicchia, o qualsiasi cosa rilasciata negli ultimi sei mesi, inventa con sicurezza. Verifica facendo girare il codice, non leggendolo.

3. Proposte di refactor

Cursor e Aider brillano entrambi qui, perché hanno tutto il tuo file (o repo) nello scope. “Rinomina process in process_invoice ovunque si riferisca al flusso fatture, lasciando il process non correlato in payments.py da solo.” Quello è un lavoro manuale di cinque minuti, sessanta secondi con un tool che può leggere tutti i call site.

I refactor più grandi (“dividi questa classe da 300 righe in tre più piccole”) richiedono più babysitting, ma l’AI è eccellente nella parte meccanica: estrarre metodi, far passare parametri, aggiornare gli import. Tu fornisci la decisione architetturale; lui fa la parte di battitura.

4. Code review

Sorprendentemente bravo. Incolla una funzione e chiedi “ci sono problemi?” e regolarmente ti torna:

  • Errori off-by-one nello slicing.
  • Mismatch di tipi che il linter si è perso.
  • Argomenti default mutabili.
  • Gestione di eccezioni mancante su una chiamata nota per essere instabile.
  • “Questa regex ha backtracking catastrofico sull’input X.”

Non è un sostituto del reviewer umano: non conosce le regole di business, il tuo budget di performance, o il tuo dominio, ma per il livello meccanico della review, l’AI è un secondo paio d’occhi forte. Adesso faccio passare le mie pull request da Claude prima di postarle e regolarmente sistemo due o tre cose prima che qualsiasi umano veda la diff.

5. Spiegazione

“Perché questo è lento?” “Cosa fa match con questa regex?” “Cosa sta facendo questo __init_subclass__?” Per codice ereditato o librerie poco familiari, la spiegazione di un assistente AI è di solito corretta, completa, e più veloce che leggere il sorgente. Per fare onboarding su una codebase nuova, è trasformativo.

L’avvertenza è la stessa della categoria 2: verifica. La spiegazione che suona giusta e quella che è giusta sono eventi diversi.

Dove gli assistenti AI fanno male

Il danno è reale, e vale la pena nominarlo esplicitamente.

Codice plausibile ma sbagliato in territorio sconosciuto. Quando l’AI non sa la risposta, non lo dice. Produce codice che compila, sembra idiomatico, ed è sbagliato in un modo che ti porta via più tempo a fare debug di quanto te ne sarebbe servito a scriverlo da solo. Questo è più doloroso nelle librerie oscure, nelle feature recenti del linguaggio, e ovunque i dati di training siano scarsi.

Sovra-ingegnerizzazione. Chiedi una funzione che fa fetch di una URL, ti torna una funzione con retry, exponential backoff, un layer di cache, logging strutturato, e un Protocol per il client HTTP. Niente di tutto questo era stato chiesto. Niente di tutto questo è sbagliato, esattamente, ma è rumore che non volevi, e ci metti più tempo a leggerlo che a scriverlo.

Ignorare le convenzioni del progetto. La tua codebase usa httpx; l’AI ti dà requests. La tua codebase usa loguru; l’AI usa logging. Il tuo team vieta try/except Exception; l’AI li sparge a piene mani. Senza contesto, l’AI ricade in uno stile generico “codice Python su internet”, non il tuo stile.

Sintassi vecchia per feature più nuove. Gli assistenti AI spesso producono Optional[X] e List[int] invece di X | None e list[int], perché i loro dati di training pendono verso lo stile più vecchio. Producono from typing import Dict quando niente lo importava. I default Python moderni vanno fatti rispettare.

La trappola Tab-Tab-Tab. Il singolo modo di fallire più grosso del 2026 è che la gente accetta i suggerimenti AI senza leggerli. L’autocomplete è veloce, i suggerimenti sembrano giusti, e tu spedisci codice che non hai mai davvero letto. È così che ottieni bug che nessuno nel team capisce, perché nessuno nel team li ha scritti.

Il workflow che funziona davvero

Qualche abitudine che trasforma l’AI da passivo a risorsa.

Type-hint su tutto. I suggerimenti AI diventano drasticamente migliori quando ci sono tipi nello scope. Il modello ha più materiale con cui lavorare, i suggerimenti diventano più specifici, e il modo di fallire passa da “ha inventato un attributo” a “ha chiamato il metodo giusto”. Questa è la cosa con la leva più alta in assoluto che puoi fare.

Mostra all’AI le tue convenzioni. Un breve CONVENTIONS.md (o STYLE.md, o quello che vuoi) fissato nel contesto dell’AI gli dice quale libreria di logging, quale client HTTP, quali pattern di testing, quale stile di gestione errori. Cursor e Aider permettono entrambi di fissare file; Copilot prende .github/copilot-instructions.md; Claude in Cursor legge CLAUDE.md. Spendi un’ora una volta su questo file e ogni prompt successivo migliora.

# Conventions

- HTTP: httpx, async by default.
- Logging: loguru, no f-strings in log messages, structured kwargs.
- Errors: domain exceptions in `errors.py`. No bare `except:`.
- Tests: pytest, parametrize over loops, fixtures in `conftest.py`.
- Style: ruff with our `pyproject.toml` config.

Rivedi ogni riga. Leggi la diff. Il fatto che non l’abbia battuta tu non conta; sei tu che la spedisci. Lo spostamento mentale è da “scrittore” a “editor”, ed editare significa leggere davvero.

Fissa l’assistente a piccoli passi. “Implementa tutta questa feature” produce una patch sparpagliata, difficile da revisionare. “Implementa solo il parser” produce una diff focalizzata che puoi leggere in un minuto. Più piccola l’unità, migliore l’output.

Pattern di prompt che funzionano

Qualche pattern che uso ogni giorno, e quelli che ho smesso di usare.

Pattern che funzionano:

  • “Scrivi una tabella parametrize di pytest per questi N casi.” Quasi sempre perfetto.
  • “Aggiungi i type hint a questa funzione.” Affidabile, basso rischio.
  • “Fai refactor di questo per usare early return.” Specifico, meccanico, facile da verificare.
  • “Spiega cosa fa questo codice, riga per riga.” Ottimo per onboarding.
  • “Ha bug? Cerca off-by-one, mismatch di tipi, e default mutabili.” Prompt di review specifico, meglio di “rivedi questo”.
  • “Converti questo unittest in pytest.” Meccanico, ben definito.

Pattern che producono risultati variabili:

  • “Scrivi una funzione che fa X.” Senza contesto, X viene interpretato generosamente e il risultato sovra-ingegnerizza.
  • “Ottimizza questo.” Ottimizza per cosa? Latenza? Memoria? Leggibilità?
  • “Rendi questo più Pythonico.” Vago. L’AI indovina, a volte bene, a volte aggiungendo astrazioni indesiderate.

Pattern da pensionare:

  • “Scrivi tutto il modulo.” Spenderai più tempo a sistemarlo che a scriverlo.
  • “Genera test per questo codice.” Tende a produrre test che riflettono l’implementazione, non il comportamento. Meglio: scrivi i nomi dei test tu, chiedi all’AI di riempire i corpi.

Un ciclo reale di umano-AI-review-spedizione

Un piccolo esempio reale. Ho una funzione che voglio scrivere: arrotonda un prezzo al centesimo più vicino, ma con il banker’s rounding.

Inizio dalla firma e da una docstring:

def round_price(amount: float) -> float:
    """Round to the nearest cent using banker's rounding (round half to even)."""

Chiedo all’AI di riempirla. Produce:

from decimal import Decimal, ROUND_HALF_EVEN

def round_price(amount: float) -> float:
    """Round to the nearest cent using banker's rounding (round half to even)."""
    return float(Decimal(str(amount)).quantize(Decimal("0.01"), rounding=ROUND_HALF_EVEN))

La leggo. La conversione str(amount) è sospetta: e se amount fosse già un Decimal? Ma la firma è float, quindi va bene. Preferirei anche restituire Decimal invece di float, ma la firma dice float. La accetto.

Poi chiedo: “Scrivi una tabella parametrize che copre: 1.005, 1.015, 1.025, -1.005, 0.0, numeri grandi.” Produce:

import pytest

@pytest.mark.parametrize("amount,expected", [
    (1.005, 1.00),
    (1.015, 1.02),
    (1.025, 1.02),
    (-1.005, -1.00),
    (0.0, 0.0),
    (1_000_000.555, 1_000_000.56),
])
def test_round_price(amount: float, expected: float) -> None:
    assert round_price(amount) == expected

Li faccio girare. Passano. Spedisco.

Tempo totale: meno di due minuti. Scritto a mano, sarebbero stati dieci minuti di battitura e una breve deviazione sui doc di decimal per ricordarmi il nome della costante. L’AI non ha fatto niente che non avrei potuto fare; ha fatto cose che avrei fatto, più velocemente.

I tradeoff dei tool del 2026, in breve

  • Copilot: il miglior autocomplete inline, debole sui refactor multi-file. Il default se vivi in VS Code e non vuoi pensarci.
  • Cursor: fork di VS Code con contesto a livello di repo di prima classe e un eccellente pannello di chat. Vale lo switch se vuoi che l’AI veda più di un file alla volta.
  • Claude in Cursor / Continue: stessa shell, modello più intelligente per i task più duri. La combinazione che uso per i refactor non banali.
  • Aider: basato su terminale, git-aware, fa i commit per te. Ottimo per refactor batch e workflow headless.
  • Codex CLI: l’agente da terminale di OpenAI, territorio simile a Aider con ergonomia diversa.

I tool convergono nel tempo; il workflow no. Type-hint, documenta le convenzioni, rivedi ogni riga, fai prompt piccoli. Se fai questo, qualsiasi di loro funziona. Se salti questo, nessuno funziona.

Cerca