Le f-string sono un modo moderno e molto comodo per incorporare valori di variabili ed espressioni direttamente nel testo di una stringa. Per creare una f-string, è sufficiente anteporre una f prima delle virgolette di apertura. All’interno della stringa, è possibile inserire variabili racchiudendole tra parentesi graffe {}, e Python sostituirà automaticamente {variabile} con il suo valore.
<em># Creiamo due variabili con valori numerici.</em>
uno = 1
due = 2
<em># Inseriamo i valori delle variabili 'uno' e 'due' nella stringa.</em>
f"{uno}, {due}"
<em># Output: '1, 2'</em>
La potenza delle f-string risiede nella loro capacità di contenere non solo variabili, ma quasi ogni espressione Python valida all’interno delle parentesi graffe. Ciò consente di eseguire calcoli al volo senza creare variabili intermedie non necessarie.
<em># Eseguiamo un'addizione direttamente all'interno della f-string.</em>
f"{uno} + {due} = {uno + due}"
<em># Output: '1 + 2 = 3'</em>
È possibile accedere facilmente agli elementi di strutture dati come dizionari (tramite chiave) e liste (tramite indice) da una f-string. Questo si estende anche all’accesso agli attributi degli oggetti (usando la notazione punto) e persino alla chiamata di funzioni.
<em># Accesso al valore di un dizionario tramite la sua chiave.</em>
colori = {"rosso": "#ff0000", "verde": "#00ff00"}
f"Il colore rosso è {colori['rosso']}"
<em># Output: 'Il colore rosso è #ff0000'</em>
<em># Accesso agli elementi di una lista tramite il loro indice.</em>
dati = [4, 8, 15, 16, 23, 42]
f"I numeri migliori: {dati[4]} e {dati[5]}"
<em># Output: 'I numeri migliori: 23 e 42'</em>
<em># Accesso agli attributi di un oggetto e chiamata di funzioni.</em>
from dataclasses import dataclass
@dataclass
class Punto:
x: int
y: int
pos = Punto(23, 42)
f"Posizione: {pos.x}, {pos.y}. Il valore più grande è {max(pos.x, pos.y)}."
<em># Output: 'Posizione: 23, 42. Il valore più grande è 42.'</em>
Usare le f-string per il debug
A partire da Python 3.8, le f-string includono un potente strumento per il debug rapido. Aggiungendo un segno di uguale (=) dopo una variabile o un’espressione all’interno delle parentesi graffe, si indica a Python di stampare non solo il valore, ma anche l’espressione stessa. Questo è incredibilmente utile per ispezionare rapidamente il codice.
from datetime import date, timedelta
utente = "eric_idle"
membro_dal = date(1975, 7, 31)
delta = date(2022, 4, 11) - membro_dal
<em># Usa f"{utente=}" per ottenere automaticamente "utente='eric_idle'".</em>
f"{utente=} {membro_dal=}"
<em># Output: "utente='eric_idle' membro_dal=datetime.date(1975, 7, 31)"</em>
Questo specificatore di debug può essere combinato con specificatori di formattazione regolari. È anche possibile utilizzarlo su intere espressioni per mostrare chiaramente sia il calcolo che il suo risultato.
<em># = mostra il nome della variabile, : inizia la formattazione, ,d formatta come intero con separatore di migliaia.</em>
f"{delta.days=:,d}"
<em># Output: 'delta.days=17,056'</em>
<em># Mostra sia l'input che il risultato di un calcolo.</em>
from math import cos, radians
theta = 30
f"{theta=} {cos(radians(theta))=:.3f}"
<em># Output: 'theta=30 cos(radians(theta))=0.866'</em>
Stampare la rappresentazione di debug (repr)
Gli oggetti Python hanno due rappresentazioni di stringa: str() (per un output di facile lettura per l’utente) e repr() (per un output non ambiguo, destinato allo sviluppatore). Per impostazione predefinita, le f-string utilizzano str(). Per ottenere l’output di repr(), è possibile utilizzare il flag di conversione !r, che è una comoda scorciatoia per chiamare repr() manualmente. Il flag !s chiama esplicitamente str().
class Dato:
def __repr__(self): return '<Dato ...>' <em># Rappresentazione di debug</em>
def __str__(self): return 'rappresentazione di stringa' <em># Rappresentazione per l'utente</em>
dato = Dato()
<em># Il comportamento predefinito chiama str(dato)</em>
f"dato: {dato}"
<em># Output: 'dato: rappresentazione di stringa'</em>
<em># Usando il flag !r si chiama repr(dato)</em>
f"dato: {dato!r}"
<em># Output: 'dato: <Dato ...>'</em>
Riempimento, allineamento e troncamento
Le f-string offrono un controllo completo sulla formattazione dell’output, inclusa la larghezza del campo, l’allineamento e i caratteri di riempimento. Lo specificatore di formato viene posto dopo i due punti (:).
>allinea il testo a destra.<allinea il testo a sinistra.^centra il testo.- Un numero dopo il carattere di allineamento specifica la larghezza totale del campo.
- Un carattere prima del carattere di allineamento specifica il carattere di riempimento.
val = "test"
<em># Allinea a destra in un campo di 10 caratteri</em>
f"{val:>10}"
<em># Output: ' test'</em>
<em># Allinea a sinistra e riempi lo spazio vuoto con '_'</em>
f"{val:_<10}"
<em># Output: 'test______'</em>
<em># Centra il testo</em>
f"{val:^10}"
<em># Output: ' test '</em>
Per troncare una stringa a una lunghezza massima, si usa la sintassi :.N, dove N è il numero di caratteri.
strumento = "xylophone"
<em># Tronca la stringa a 5 caratteri.</em>
f"{strumento:.5}"
<em># Output: 'xylop'</em>
Rappresentazioni numeriche
Sono disponibili numerose opzioni di formattazione per i numeri. È possibile convertire facilmente i numeri in binario (b), ottale (o) o esadecimale (x/X). L’uso di un # aggiunge il prefisso appropriato (0b, 0o, 0x).
risposta = 42
<em># Rappresentazione esadecimale</em>
f"{risposta:x}, {risposta:X}"
<em># Output: '2a, 2A'</em>
<em># Con un prefisso</em>
f"{risposta:#x}"
<em># Output: '0x2a'</em>
<em># Notazione scientifica</em>
f"{risposta ** 8:e}"
<em># Output: '9.682652e+12'</em>
Il riempimento dei numeri con zeri iniziali si ottiene usando 0 come carattere di riempimento. Per i numeri in virgola mobile, è possibile controllare sia la larghezza totale che il numero di cifre decimali. È anche possibile controllare la visualizzazione del segno e aggiungere separatori di migliaia.
<em># Riempi fino a 4 cifre con zeri iniziali</em>
f"{risposta:04d}"
<em># Output: '0042'</em>
<em># Per Pi greco: larghezza totale 6, 2 cifre decimali, riempimento con zeri</em>
import math
f"{math.pi:06.2f}"
<em># Output: '003.14'</em>
<em># Mostra sempre il segno (+ per i positivi, - per i negativi)</em>
f"{risposta:+d}"
<em># Output: '+42'</em>
<em># Usa la virgola come separatore di migliaia</em>
num = 1234567890
f"{num:,d}"
<em># Output: '1,234,567,890'</em>
Argomenti aggiuntivi e buone pratiche
Molti oggetti integrati, come datetime, hanno il loro mini-linguaggio di formattazione che funziona direttamente all’interno delle f-string.
from datetime import datetime
dt = datetime(2022, 4, 11, 13, 37)
<em># Usa i codici di formattazione standard per data/ora</em>
f"{dt:%Y-%m-%d %H:%M}"
<em># Output: '2022-04-11 13:37'</em>
Gli specificatori di formato stessi non devono essere statici! È possibile impostarli dinamicamente usando variabili con parentesi graffe annidate.
<em># Larghezza e allineamento sono impostati da variabili</em>
valore = "test"
allineamento = "^"
larghezza = 10
<em># f'{valore:{allineamento}{larghezza}}' prima diventa f'{valore:^10}'</em>
f'{valore:{allineamento}{larghezza}}'
<em># Output: ' test '</em>
Se hai bisogno di includere una parentesi graffa letterale { o } nella tua stringa, è sufficiente raddoppiarla.
f"Parentesi graffe letterali: {{valore}}"
<em># Output: 'Parentesi graffe letterali: {valore}'</em>
Consigli pratici e possibili insidie
Leggibilità vs. brevità
Le f-string consentono di incorporare espressioni molto complesse, ma non è sempre una buona idea. Un principio fondamentale di Python è la leggibilità del codice.
Cattiva pratica (difficile da leggere):
<em># Tecnicamente funziona, ma cosa sta succedendo qui?</em>
print(f"Risultato: {round([user.height for user in users if user.active][0] / 2.54, 2)} pollici.")
Buona pratica (separare la logica dalla presentazione):
<em># 1. Prima, prepariamo i dati</em>
altezze_utenti_attivi = [user.height for user in users if user.active]
altezza_primo_utente_cm = altezze_utenti_attivi[0]
altezza_in_pollici = round(altezza_primo_utente_cm / 2.54, 2)
<em># 2. Poi, formattiamo il risultato</em>
print(f"Risultato: {altezza_in_pollici} pollici.")
Il codice è più lungo, ma ora è comprensibile, testabile e facile da modificare. Una regola generale: se un’espressione all’interno di {} è più lunga di 20-30 caratteri o contiene una logica complessa, è meglio estrarla in una variabile separata.
F-string su più righe
Per stringhe lunghe o per generare testo su più righe (ad es. query SQL, template HTML), usa le virgolette triple. Anche le espressioni al loro interno possono estendersi su più righe.
utente = "hypo69"
permessi = ['read', 'write', 'execute']
<em># L'espressione all'interno della f-string è suddivisa su più righe per leggibilità</em>
report = f"""
Report Utente: {utente.upper()}
----------------------------------
Permessi: {', '.join(p for p in permessi
if p != 'execute')}
"""
print(report)
Questioni di sicurezza (Molto importante!)
Le f-string di per sé sono sicure. Un’espressione come f"Ciao, {nome}" non è una vulnerabilità; Python prende semplicemente la rappresentazione di stringa di nome.
Il pericolo sorge quando si combinano le f-string con funzioni che eseguono codice, come eval().
CODICE ESTREMAMENTE PERICOLOSO — NON FARLO MAI:
import os
template_utente = input("Inserisci un template: ") <em># L'utente inserisce: '{os.system("reboot")}'</em>
eval(f'f"{template_utente}"')
Se un utente inserisce {os.system("reboot")}, il tuo server si riavvierà. Questa è una vulnerabilità di Esecuzione di Codice Remoto (RCE). Non passare mai stringhe fornite dall’utente a funzioni come eval(), nemmeno tramite f-string. Per template definiti dall’utente, usa motori di templating sicuri come Jinja2.
Prestazioni: perché le f-string sono così veloci?
Una f-string non è una chiamata di funzione; è parte della sintassi di Python. Il compilatore analizza la f-string e la suddivide in parti letterali ed espressioni da valutare. Questo viene compilato in un bytecode altamente efficiente che costruisce la stringa finale in un’unica operazione veloce. È molto più veloce del metodo .format(), che deve analizzare la stringa di formato a runtime.
Strumenti di migrazione
Strumenti come flynt e pyupgrade possono convertire automaticamente codice datato che usa .format() e % in moderne f-string, il che è un ottimo modo per modernizzare rapidamente una codebase legacy.