In questa lezione esploriamo argomenti opzionali e scope in Python, imparando a definire valori di default, usare *args e **kwargs, dichiarare argomenti keyword‑only e comprendere come funzionano variabili locali, globali e lo scope (regola LEGB). Vedrai anche come creare funzioni annidate e closure con nonlocal. Concludiamo con esempi pratici, esercizi, quiz e risorse.
🔗 Ripassi consigliati (interni):
• Lezione 2 – Il tuo primo script: Hello, Python! → https://www.saperesemplice.com/primo-script-python-hello/
• Lezione 3 – Variabili e Tipi di Dato → https://www.saperesemplice.com/variabili-tipi-dato-python/
• Lezione 9 – Cicloforerange()→ https://www.saperesemplice.com/ciclo-for-range-python/
• Lezione 6 – Condizioni conif,else,elif→ https://www.saperesemplice.com/condizioni-if-else-elif-python/
Dettagli lezione
⏱️ Tempo stimato: 45–55 minuti
🧩 Difficoltà: Base → Intermedio
🎯 Obiettivi specifici
- Definire argomenti opzionali con valori di default in modo sicuro.
- Usare
*argse**kwargs, oltre agli argomenti keyword‑only (dopo*). - Comprendere scope e namespace: regola LEGB e comportamento dei nomi.
- Sapere quando (e come) usare
globalenonlocal. - Scrivere funzioni annidate e closure per incapsulare stato.
- Evitare errori comuni (default mutabili, shadowing di nomi, leaky abstractions).
🧱 Argomenti opzionali con valori di default
Gli argomenti opzionali hanno un valore di default nella firma della funzione. Se il chiamante non fornisce l’argomento, viene usato il default.
def greet(name, greeting="Hello"):
return f"{greeting}, {name}!"
print(greet("Alice")) # Hello, Alice!
print(greet("Bob", greeting="Hi")) # Hi, Bob!
⚠️ Errore comune da evitare – default mutabili
Non usare liste/dict/set come default: vengono valutati una sola volta, alla definizione della funzione. Tutte le chiamate con quel default condividono lo stesso oggetto.❌ Sbagliato:
def add_item(x, bucket=[]): bucket.append(x) return bucket✔️ Corretto (usa
Nonecome sentinella):def add_item(x, bucket=None): if bucket is None: bucket = [] bucket.append(x) return bucket
Domanda per te: quali valori di default sono sempre sicuri? (Suggerimento: oggetti immutabili come numeri, stringhe, tuple.)
🔁 *args e **kwargs + keyword‑only
*argsraccoglie argomenti posizionali variabili in una tupla.**kwargsraccoglie argomenti keyword in un dizionario.- Gli argomenti keyword‑only si dichiarano dopo un
*nella firma: possono essere passati solo per nome.
def report(title, *items, sep=", ", uppercase=False, **meta):
text = sep.join(str(x) for x in items)
if uppercase:
text = text.upper()
return f"{title}: {text} | meta={meta}"
print(report("Numbers", 1, 2, 3, sep=" - ", uppercase=True, author="Alessio"))
# Numbers: 1 - 2 - 3 | meta={'author': 'Alessio'}
Keyword‑only espliciti
def save(path, *, overwrite=False, encoding="utf-8"):
# 'overwrite' ed 'encoding' sono keyword‑only
return f"Saving to {path} with overwrite={overwrite}, encoding={encoding}"
print(save("/tmp/out.txt", overwrite=True))
💡 I keyword‑only migliorano la leggibilità delle chiamate e riducono ambiguità su parametri booleani.
Errore comune da evitare
Passare due volte lo stesso argomento (posizionale e keyword):
f(1, x=1)→TypeError.
🧭 Scope e namespace: la regola LEGB
Lo scope è la regione testuale in cui un nome è visibile. Python cerca i nomi in questa sequenza:
- Local (della funzione corrente),
- Enclosing (funzioni esterne annidate),
- Global (modulo corrente),
- Built‑in (nomi predefiniti come
len,print).
x = "global"
def outer():
x = "enclosing"
def inner():
x = "local"
return x
return inner()
print(outer()) # local
🔎 Assegnare a un nome dentro una funzione lo rende, per default, locale a quella funzione. Senza dichiarazioni speciali, le assegnazioni non toccano nomi globali.
Shadowing (ombreggiamento)
Evitare di riusare nomi come list, sum, dir: nascondono i built‑in e creano confusione.
🌍 global e 🧭 nonlocal
global namedice al compilatore che tutte le assegnazioni anamenella funzione si riferiscono al nome globale (del modulo).nonlocal namepermette di riassegnare un nome nell’enclosing scope (funzione esterna), non globale.
# Esempio global
counter = 0
def tick_global():
global counter
counter += 1
return counter
# Esempio nonlocal
def make_counter():
count = 0
def inc():
nonlocal count
count += 1
return count
return inc
c1 = make_counter()
print(c1()) # 1
print(c1()) # 2
⚠️ Attenzione: usa
globalcon parsimonia: rende il codice più difficile da testare. Preferisci ritornare valori o usare closure/oggetti.
Errore comune da evitare
Usare
nonlocal xquando non esiste unxnell’enclosing scope →SyntaxError. Definisci primaxnell’outer function.
🧩 Funzioni annidate e closure
Una closure è una funzione che cattura variabili dal suo enclosing scope.
def make_formatter(prefix="[LOG]"):
def format_message(msg):
return f"{prefix} {msg}"
return format_message
log = make_formatter(prefix="[APP]")
print(log("Started")) # [APP] Started
Con stato modificabile con nonlocal
def make_limiter(limit):
used = 0
def use(n=1):
nonlocal used
used += n
return used <= limit
return use
is_allowed = make_limiter(3)
print(is_allowed()) # True (1)
print(is_allowed()) # True (2)
print(is_allowed()) # True (3)
print(is_allowed()) # False (4)
Domanda per te: in quali casi useresti una closure invece di una variabile globale? Fai un esempio concreto del tuo progetto.
🔶 Esempi pratici guidati
1) API user‑friendly con keyword‑only
def resize(image, *, width=None, height=None, keep_aspect=True):
"""Return a resize plan; demo only."""
return {
"width": width,
"height": height,
"keep_aspect": keep_aspect,
}
print(resize("img.jpg", width=800))
2) Somma sicura con *args e default immutabili
def sum_safe(*nums, start=0):
total = start
for n in nums:
total += n
return total
print(sum_safe(1, 2, 3)) # 6
print(sum_safe(1, 2, 3, start=10)) # 16
3) Router con **kwargs (dispatch per nome)
def route(action, **handlers):
handler = handlers.get(action, lambda: "Unknown action")
return handler()
print(route("create", create=lambda: "Created"))
4) Closure contatore formato (con nonlocal)
def make_id(prefix="USR"):
i = 0
def next_id():
nonlocal i
i += 1
return f"{prefix}-{i:03d}"
return next_id
uid = make_id("ORD")
print(uid()) # ORD-001
print(uid()) # ORD-002
💬 Domanda per il lettore: quali parametri renderesti keyword‑only in una tua funzione esistente per migliorarne la leggibilità?
📖 Sezione teorica
- Default arguments: valutati una sola volta alla definizione → evita mutabili come default; usa
None+ inizializzazione interna. *args/**kwargs: collezionano argomenti variabili; keyword‑only dopo*migliorano chiarezza.- LEGB: ricerca nomi in Local → Enclosing → Global → Built‑in.
globalriassegna nel modulo;nonlocalriassegna nell’enclosing.- Closure: funzioni annidate che catturano variabili; usa
nonlocalper aggiornare stato.
📚 Glossario tecnico
- Scope: regione in cui un nome è visibile e risolvibile.
- Namespace: insieme di associazioni nome → oggetto (es.
globals(),locals()). - LEGB: ordine di risoluzione dei nomi.
- Keyword‑only: parametro che può essere passato solo per nome (
def f(a, *, b): ...). - Closure: funzione che conserva riferimenti a variabili non locali.
📝 Esercizio finale
Realizza un mini pacchetto di utilità composto da:
make_logger(prefix="[APP]")→ ritorna una closure che stampa/ritorna messaggi formattati; usanonlocalper contare quante volte è stata chiamata (esporrecount).accumulate(*nums, start=0, *, op="sum")→ supporta operazioni"sum"e"product";opè keyword‑only; usa default immutabili.configure(**options)→ accetta qualunque chiave, ma valida chemode(se presente) sia in{"dev","prod"}.
Requisiti:
- Messaggi di output in inglese.
- Evita
global; usa closure o ritorni di valori. - Scrivi una demo che mostri errori comuni (es. default mutabili) e la versione corretta.
Schema d’aiuto:
# Your code here...
logger = make_logger(prefix="[UTIL]")
print(logger("Init"))
print(accumulate(1, 2, 3, start=10))
print(accumulate(2, 3, 4, start=1, op="product"))
print(configure(mode="dev", retries=3))
🧠 Quiz finale
Non guardare le risposte subito! Sono più sotto 👇
- Cosa succede ai valori di default in Python?
A) Sono valutati a ogni chiamata
B) Sono valutati una volta alla definizione
C) Dipende dal tipo - Quale firma rende
limitkeyword‑only?
A)def f(limit, *args): ...
B)def f(*args, limit): ...
C)def f(limit=10): ... - In assenza di
global/nonlocal, un’assegnazione dentro una funzione…
A) Modifica il globale se esiste
B) Crea/usa un nome locale
C) Fallisce conNameError - Cosa fa
nonlocal?
A) Crea una variabile globale
B) Permette di riassegnare un nome nell’enclosing scope
C) Importa un nome dal modulo - Quale default è sicuro?
A)[]
B){}
C)0 - Quale chiamata è valida data
def save(p, *, overwrite=False): ...?
A)save("a.txt", True)
B)save("a.txt", overwrite=True)
C)save(p="a.txt", True) - In una closure, dove viene cercato il nome catturato?
A) Nel Local della inner function
B) Nell’Enclosing della outer function
C) Nel Built‑in - Cosa evita l’uso di
*args/**kwargs?
A) Firme troppo rigide
B) Il bisogno dei default
C) La necessità di documentare i parametri
❓ FAQ
1) È meglio usare global o passare valori di ritorno?
Meglio ritornare valori (o usare closure/oggetti). global è una scorciatoia che spesso rende il codice meno testabile.
2) Posso combinare *args e **kwargs con parametri normali?
Sì: l’ordine tipico è posizionali, *args, keyword‑only, **kwargs.
3) Le closure sono lente?
No in modo rilevante per i casi comuni. Vantaggio principale: incapsulazione dello stato.
4) Come documento bene una funzione con molti parametri?
Usa docstring chiare e keyword‑only per i parametri più ambigui; aggiungi esempi d’uso.
5) Posso rendere posizionali‑only alcuni parametri?
Sì (sintassi / nella firma), ma è un tema avanzato: utile per API pulite e performance.
🔗 Link utili
Esterni autorevoli
• More on Defining Functions (default, keyword‑only, *args/**kwargs) → https://docs.python.org/3/tutorial/controlflow.html#more-on-defining-functions
• Scopes and Namespaces (LEGB, binding) → https://docs.python.org/3/tutorial/classes.html#python-scopes-and-namespaces
• Reference – global e nonlocal → https://docs.python.org/3/reference/simple_stmts.html
• PEP 3102 – Keyword‑Only Arguments → https://peps.python.org/pep-3102/
• PEP 3104 – Access to Names in Outer Scopes → https://peps.python.org/pep-3104/
SapereSemplice
• Lezione 2 – Primo script Python → https://www.saperesemplice.com/primo-script-python-hello/
• Lezione 3 – Variabili e Tipi di Dato → https://www.saperesemplice.com/variabili-tipi-dato-python/
• Lezione 9 – Ciclo for e range() → https://www.saperesemplice.com/ciclo-for-range-python/
• Lezione 6 – Condizioni con if, else, elif → https://www.saperesemplice.com/condizioni-if-else-elif-python/
🔁 Collegamento alla lezione successiva
➡️ Prosegui alla Lezione 15: Importare moduli in Python
(Il link verrà inserito non appena l’articolo sarà online.)
🗨️ Hai dubbi? Lascia un commento!
📩 Vuoi il PDF della lezione? Scrivici!
🔗 Condividi con un compagno di corso.
🧪 Risposte del quiz
- B
- B
- B
- B
- C
- B
- B
- A
