4. Altri Strumenti di Controllo del Flusso
******************************************

Oltre all'istruzione "while" appena introdotta, Python ne utilizza
altre che incontreremo in questo capitolo.


4.1. Istruzioni "if"
====================

Forse il tipo di istruzione più conosciuto è l'istruzione "if".  Ad
esempio:

   >>> x = int(input("Please enter an integer: "))
   Please enter an integer: 42
   >>> if x < 0:
   ...     x = 0
   ...     print('Negative changed to zero')
   ... elif x == 0:
   ...     print('Zero')
   ... elif x == 1:
   ...     print('Single')
   ... else:
   ...     print('More')
   ...
   More

Possiamo avere zero o più parti "elif", e la parte "else" è
facoltativa. La parola chiave '"elif"' è l'abbreviazione di 'else if',
ed è utile per evitare un'eccessiva indentazione. Una sequenza "if"
... "elif" ... "elif" ... è un sostituto per le istruzioni "switch" o
"case" trovate in altri linguaggi.

Se stai confrontando lo stesso valore con diverse costanti, o
controllando tipi o attributi specifici, potresti trovare utile
l'istruzione "match". Per maggiori dettagli, consulta Istruzioni
match.


4.2. Istruzioni "for"
=====================

L'istruzione "for" in Python differisce leggermente da ciò a cui
potresti essere abitualmente abituato in C o Pascal. Piuttosto che
iterare sempre su una progressione aritmetica di numeri (come in
Pascal), o dare all'utente la possibilità di definire sia il passo di
iterazione che la condizione di arresto (come in C), l'istruzione
"for" di Python itera sugli elementi di qualsiasi sequenza (una lista
o una stringa), nell'ordine in cui appaiono nella sequenza. Ad
esempio:

   >>> # Measure some strings:
   >>> words = ['cat', 'window', 'defenestrate']
   >>> for w in words:
   ...     print(w, len(w))
   ...
   cat 3
   window 6
   defenestrate 12

Il codice che modifica una collezione durante l'iterazione sulla
stessa può essere difficile da scrivere correttamente. Invece, è
generalmente più semplice iterare su una copia della collezione o
crearne una nuova:

   # Create a sample collection
   users = {'Hans': 'active', 'Éléonore': 'inactive', '景太郎': 'active'}

   # Strategy:  Iterate over a copy
   for user, status in users.copy().items():
       if status == 'inactive':
           del users[user]

   # Strategy:  Create a new collection
   active_users = {}
   for user, status in users.items():
       if status == 'active':
           active_users[user] = status


4.3. La Funzione "range()"
==========================

Se hai bisogno di iterare su una sequenza di numeri, la funzione
integrata "range()" è utile.  Genera progressioni aritmetiche:

   >>> for i in range(5):
   ...     print(i)
   ...
   0
   1
   2
   3
   4

Il valore finale fornito non fa parte della sequenza generata;
"range(10)" genera 10 valori, gli indici legali per gli elementi di
una sequenza di lunghezza 10. È possibile far partire l'intervallo da
un altro numero, o specificare un incremento diverso (anche negativo;
talvolta questo è chiamato 'passo'):

   >>> list(range(5, 10))
   [5, 6, 7, 8, 9]

   >>> list(range(0, 10, 3))
   [0, 3, 6, 9]

   >>> list(range(-10, -100, -30))
   [-10, -40, -70]

Per iterare sugli indici di una sequenza, puoi combinare "range()" e
"len()" nel seguente modo:

   >>> a = ['Mary', 'had', 'a', 'little', 'lamb']
   >>> for i in range(len(a)):
   ...     print(i, a[i])
   ...
   0 Mary
   1 had
   2 a
   3 little
   4 lamb

Nella maggior parte di questi casi, però, è conveniente utilizzare la
funzione "enumerate()", vedi Tecniche di Looping.

Attenzione: succede una cosa strana se provi a stampare un range:

   >>> range(10)
   range(0, 10)

In molti casi, l'oggetto restituito da "range()" si comporta come se
fosse una lista, ma in realtà non lo è. È un oggetto che restituisce
gli elementi successivi della sequenza desiderata quando ci si itera
sopra, ma non crea effettivamente la lista, risparmiando così spazio.

Diciamo che un tale oggetto è un *iterable*, cioè adatto per funzioni
e costrutti che si aspettano qualcosa da cui possono ottenere elementi
fino a quando il contenitore è vuoto. Abbiamo visto che l'istruzione
"for" è un tale costrutto, mentre un esempio di una funzione che
prende come argomento un oggetto iterabile è "sum()":

   >>> sum(range(4))  # 0 + 1 + 2 + 3
   6

In seguito vedremo altre funzioni che restituiscono oggetti iterabili
e prendono oggetti iterabili come argomenti. Nel capitolo Strutture
Dati, discuteremo in modo più dettagliato riguardo a "list()".


4.4. Istruzioni "break" e "continue", e clausole "else" nei cicli
=================================================================

L'istruzione "break" esce dal ciclo "for" o "while" più interno.

Un ciclo "for" o "while" può includere una clausola "else".

In un ciclo "for", la clausola "else" viene eseguita dopo che il ciclo
ha raggiunto la sua iterazione finale.

In un ciclo "while", viene eseguita dopo che la condizione del ciclo
diventa falsa.

In entrambi i tipi di ciclo, la clausola "else" **non** viene eseguita
se il ciclo è stato terminato da un "break".

Questo è esemplificato nel seguente ciclo "for", che cerca numeri
primi:

   >>> for n in range(2, 10):
   ...     for x in range(2, n):
   ...         if n % x == 0:
   ...             print(n, 'equals', x, '*', n//x)
   ...             break
   ...     else:
   ...         # loop fell through without finding a factor
   ...         print(n, 'is a prime number')
   ...
   2 is a prime number
   3 is a prime number
   4 equals 2 * 2
   5 is a prime number
   6 equals 2 * 3
   7 is a prime number
   8 equals 2 * 4
   9 equals 3 * 3

(Sì, questo codice è corretto. Guarda attentamente: la clausola "else"
appartiene al ciclo "for", **non** all'istruzione "if".)

Quando viene utilizzata con un ciclo, la clausola "else" ha più in
comune con la clausola "else" di un'istruzione "try" rispetto a quella
delle istruzioni "if": la clausola "else" di un'istruzione "try" viene
eseguita quando non si verifica alcuna eccezione, e la clausola "else"
di un ciclo viene eseguita quando non si verifica alcun "break". Per
ulteriori informazioni sull'istruzione "try" e sulle eccezioni,
consulta Gestione delle Eccezioni.

L'istruzione "continue", presa in prestito dal C, continua con la
prossima iterazione del ciclo:

   >>> for num in range(2, 10):
   ...     if num % 2 == 0:
   ...         print("Found an even number", num)
   ...         continue
   ...     print("Found an odd number", num)
   ...
   Found an even number 2
   Found an odd number 3
   Found an even number 4
   Found an odd number 5
   Found an even number 6
   Found an odd number 7
   Found an even number 8
   Found an odd number 9


4.5. Istruzioni "pass"
======================

L'istruzione "pass" non fa nulla. Può essere utilizzata quando è
richiesta un'istruzione sintatticamente, ma il programma non richiede
alcuna azione. Per esempio:

   >>> while True:
   ...     pass  # Busy-wait for keyboard interrupt (Ctrl+C)
   ...

Questo è comunemente usato per creare classi minime:

   >>> class MyEmptyClass:
   ...     pass
   ...

Un altro posto in cui "pass" può essere usato è come segnaposto per il
corpo di una funzione o condizionale quando si sta lavorando su nuovo
codice, permettendo di continuare a pensare a un livello più astratto.
L'istruzione "pass" viene ignorata silenziosamente:

   >>> def initlog(*args):
   ...     pass   # Remember to implement this!
   ...


4.6. Istruzioni "match"
=======================

Un'istruzione "match" prende un'espressione e confronta il suo valore
con schemi successivi forniti come uno o più blocchi case. Questo è
superficialmente simile a un'istruzione switch in C, Java o JavaScript
(e molti altri linguaggi), ma è più simile al pattern matching in
linguaggi come Rust o Haskell. Solo il primo schema che corrisponde
viene eseguito e può anche estrarre componenti (elementi di sequenze o
attributi di oggetti) dal valore in variabili.

La forma più semplice confronta un valore con uno o più letterali:

   def http_error(status):
       match status:
           case 400:
               return "Bad request"
           case 404:
               return "Not found"
           case 418:
               return "I'm a teapot"
           case _:
               return "Something's wrong with the internet"

Nota l'ultimo blocco: il "nome della variabile" "_" funge da
*wildcard* e non fallisce mai. Se nessun case corrisponde, nessuno dei
rami viene eseguito.

Puoi combinare diversi letterali in un singolo schema usando "|"
("or"):

   case 401 | 403 | 404:
       return "Not allowed"

Gli schemi possono sembrare assegnazioni di decomposizione e possono
essere utilizzati per associare variabili:

   # point is an (x, y) tuple
   match point:
       case (0, 0):
           print("Origin")
       case (0, y):
           print(f"Y={y}")
       case (x, 0):
           print(f"X={x}")
       case (x, y):
           print(f"X={x}, Y={y}")
       case _:
           raise ValueError("Not a point")

Studialo attentamente! Il primo schema ha due letterali, e può essere
pensato come un'estensione dello schema letterale mostrato sopra. Ma i
successivi due schemi combinano un letterale e una variabile, e la
variabile *associa* un valore dal soggetto ("point"). Il quarto schema
cattura due valori, che lo rende concettualmente simile
all'assegnazione con *unpacking* "(x, y) = point".

Se stai utilizzando classi per strutturare i tuoi dati, puoi usare il
nome della classe seguito da un elenco di argomenti che assomiglia a
un costruttore, ma con la capacità di catturare attributi in
variabili:

   class Point:
       def __init__(self, x, y):
           self.x = x
           self.y = y

   def where_is(point):
       match point:
           case Point(x=0, y=0):
               print("Origin")
           case Point(x=0, y=y):
               print(f"Y={y}")
           case Point(x=x, y=0):
               print(f"X={x}")
           case Point():
               print("Somewhere else")
           case _:
               print("Not a point")

Puoi utilizzare parametri posizionali con alcune classi integrate che
forniscono un ordinamento per i loro attributi (ad esempio le
dataclassi). Puoi anche definire una posizione specifica per gli
attributi negli schemi impostando l'attributo speciale
"__match_args__" nelle tue classi. Se è impostato su ("x", "y"), gli
schemi seguenti sono tutti equivalenti (e tutti associano l'attributo
"y" alla variabile "var"):

   Point(1, var)
   Point(1, y=var)
   Point(x=1, y=var)
   Point(y=var, x=1)

Un modo consigliato per leggere gli schemi è considerarli come una
forma estesa di quello che metteresti a sinistra di un'assegnazione,
per capire quali variabili verrebbero impostate su cosa. Solo i nomi
autonomi (come "var" qui sopra) vengono assegnati da un'istruzione di
match. I nomi puntati (come "foo.bar"), i nomi degli attributi (il
"x=" e "y=" qui sopra) o i nomi delle classi (riconosciuti dalle
"(...)" accanto a loro come "Point" qui sopra) non vengono mai
assegnati.

Gli schemi possono essere arbitrariamente nidificati. Ad esempio, se
abbiamo una breve lista di Punti, con "__match_args__" aggiunto,
potremmo abbinarla così:

   class Point:
       __match_args__ = ('x', 'y')
       def __init__(self, x, y):
           self.x = x
           self.y = y

   match points:
       case []:
           print("No points")
       case [Point(0, 0)]:
           print("The origin")
       case [Point(x, y)]:
           print(f"Single point {x}, {y}")
       case [Point(0, y1), Point(0, y2)]:
           print(f"Two on the Y axis at {y1}, {y2}")
       case _:
           print("Something else")

Possiamo aggiungere una clausola "if" a uno schema, nota come
"guardia". Se la guardia è falsa, "match" passa a provare il blocco
case successivo. Nota che la cattura del valore avviene prima che la
guardia venga valutata:

   match point:
       case Point(x, y) if x == y:
           print(f"Y=X at {x}")
       case Point(x, y):
           print(f"Not on the diagonal")

Altre caratteristiche chiave di questa istruzione:

* Come le assegnazioni di decomposizione, gli schemi di tuple e liste
  hanno esattamente lo stesso significato e corrispondono
  effettivamente a sequenze arbitrarie. Un'importante eccezione è che
  non corrispondano a iteratori o stringhe.

* Le sequenze di schemi supportano l'unpacking esteso: "[x, y, *rest]"
  e "(x, y, *rest)" funzionano in modo simile alle assegnazioni di
  decomposizione. Il nome dopo "*" può anche essere "_", quindi "(x,
  y, *_)" corrisponde a una sequenza di almeno due elementi senza la
  necessità di associare gli elementi rimanenti.

* Schemi di mapping: "{"bandwidth": b, "latency": l}" cattura i valori
  ""bandwidth"" e ""latency"" da un dizionario. A differenza degli
  schemi di sequenza, le chiavi aggiuntive vengono ignorate. È
  supportato anche un unpacking come "**rest". (Ma "**_" sarebbe
  ridondante, quindi non è possibile utilizzarlo.)

* I sotto-schemi possono essere catturati utilizzando la parola chiave
  "as":

     case (Point(x1, y1), Point(x2, y2) as p2): ...

  acquisirà il secondo elemento dell'input come "p2" (a patto che
  l'input sia una sequenza di due punti)

* La maggior parte dei letterali sono confrontati per uguaglianza,
  tuttavia i *singleton* "True", "False" e "None" sono confrontati per
  identità.

* Gli schemi possono utilizzare costanti nominate. Queste devono
  essere nomi puntati per impedire che vengano interpretati come
  variabili di cattura:

     from enum import Enum
     class Color(Enum):
         RED = 'red'
         GREEN = 'green'
         BLUE = 'blue'

     color = Color(input("Enter your choice of 'red', 'blue' or 'green': "))

     match color:
         case Color.RED:
             print("I see red!")
         case Color.GREEN:
             print("Grass is green")
         case Color.BLUE:
             print("I'm feeling the blues :(")

Per una spiegazione più dettagliata e ulteriori esempi, puoi
consultare **PEP 636** che è scritta in un formato simile a un
tutorial.


4.7. Definizione di Funzioni
============================

Possiamo creare una funzione che scrive la serie di Fibonacci fino a
un limite arbitrario:

   >>> def fib(n):    # write Fibonacci series up to n
   ...     """Print a Fibonacci series up to n."""
   ...     a, b = 0, 1
   ...     while a < n:
   ...         print(a, end=' ')
   ...         a, b = b, a+b
   ...     print()
   ...
   >>> # Now call the function we just defined:
   >>> fib(2000)
   0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597

La parola chiave "def" introduce una *definizione* di funzione. Deve
essere seguita dal nome della funzione e dall'elenco tra parentesi dei
parametri formali. Le istruzioni che formano il corpo della funzione
iniziano alla riga successiva, e devono essere indentate.

La prima istruzione del corpo della funzione può opzionalmente essere
una stringa letterale; questa stringa letterale è la stringa di
documentazione della funzione, o *docstring*. (Maggiori informazioni
sulle docstring possono essere trovate nella sezione Stringhe di
Documentazione.) Ci sono strumenti che utilizzano le docstring per
produrre automaticamente documentazione online o stampata, o per
consentire all'utente di navigare interattivamente attraverso il
codice; è una buona pratica includere le docstring nel codice che
scrivi, quindi prendilo come abitudine.

L'*esecuzione* di una funzione introduce una nuova tabella dei simboli
utilizzata per le variabili locali della funzione. Più precisamente,
tutte le assegnazioni di variabili in una funzione memorizzano il
valore nella tabella dei simboli locali; mentre i riferimenti alle
variabili cercano prima nella tabella dei simboli locali, poi nelle
tabelle dei simboli locali delle funzioni circostanti, poi nella
tabella dei simboli globali, e infine nella tabella dei nomi
incorporati. Pertanto, le variabili globali e le variabili delle
funzioni circostanti non possono essere direttamente assegnate un
valore all'interno di una funzione (a meno che, per le variabili
globali, siano nominate in un'istruzione "global", o, per le variabili
delle funzioni circostanti, siano nominate in un'istruzione
"nonlocal"), anche se possono essere referenziate.

I parametri effettivi (argomenti) di una chiamata di funzione vengono
introdotti nella tabella dei simboli locali della funzione chiamata
quando viene chiamata; quindi, gli argomenti sono passati utilizzando
il *passaggio per valore* (dove il *valore* è sempre un *riferimento*
all'oggetto, non il valore dell'oggetto). [1] Quando una funzione
chiama un'altra funzione, o si chiama ricorsivamente, viene creata una
nuova tabella dei simboli locali per quella chiamata.

Una definizione di funzione associa il nome della funzione all'oggetto
funzione nella tabella dei simboli corrente. L'interprete riconosce
l'oggetto puntato da quel nome come una funzione definita dall'utente.
Altri nomi possono anche puntare a quello stesso oggetto funzione e
possono anche essere utilizzati per accedere alla funzione:

   >>> fib
   <function fib at 10042ed0>
   >>> f = fib
   >>> f(100)
   0 1 1 2 3 5 8 13 21 34 55 89

Provenendo da altri linguaggi, potresti obiettare che "fib" non è una
funzione ma una procedura poiché non restituisce un valore. Infatti,
anche le funzioni senza un'istruzione "return" restituiscono un
valore, sebbene piuttosto noioso. Questo valore si chiama "None" (è un
nome riservato del linguaggio). Scrivere il valore "None" è
normalmente soppresso dall'interprete se sarebbe l'unico valore
scritto. Puoi vederlo se lo desideri davvero utilizzando "print()":

   >>> fib(0)
   >>> print(fib(0))
   None

È semplice scrivere una funzione che restituisce una lista dei numeri
della serie di Fibonacci, invece di stamparla:

   >>> def fib2(n):  # return Fibonacci series up to n
   ...     """Return a list containing the Fibonacci series up to n."""
   ...     result = []
   ...     a, b = 0, 1
   ...     while a < n:
   ...         result.append(a)    # see below
   ...         a, b = b, a+b
   ...     return result
   ...
   >>> f100 = fib2(100)    # call it
   >>> f100                # write the result
   [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

Questo esempio, come al solito, dimostra alcune nuove funzionalità di
Python:

* L'istruzione "return" restituisce un valore da una funzione.
  "return" senza un argomento espressione restituisce "None". Anche
  uscire dalla fine di una funzione restituisce "None".

* L'istruzione "result.append(a)" chiama un *metodo* dell'oggetto
  lista "result". Un metodo è una funzione che 'appartiene' a un
  oggetto e si chiama "obj.methodname", dove "obj" è un qualche
  oggetto (questo può essere un'espressione), e "methodname" è il nome
  di un metodo definito dal tipo dell'oggetto. Diversi tipi
  definiscono metodi diversi. Metodi di tipi diversi possono avere lo
  stesso nome senza causare ambiguità. (È possibile definire i propri
  tipi di oggetto e metodi, utilizzando *classi*, vedi Classi) Il
  metodo "append()" mostrato nell'esempio è definito per gli oggetti
  lista; aggiunge un nuovo elemento alla fine della lista. In questo
  esempio è equivalente a "result = result + [a]", ma più efficiente.


4.8. Ulteriori informazioni sulla Definizione di Funzioni
=========================================================

È anche possibile definire funzioni con un numero variabile di
argomenti. Esistono tre forme, che possono essere combinate.


4.8.1. Valori predefiniti degli argomenti
-----------------------------------------

La forma più utile è specificare un valore predefinito per uno o più
argomenti. Questo crea una funzione che può essere chiamata con meno
argomenti di quelli che è definita per consentire. Ad esempio:

   def ask_ok(prompt, retries=4, reminder='Please try again!'):
       while True:
           reply = input(prompt)
           if reply in {'y', 'ye', 'yes'}:
               return True
           if reply in {'n', 'no', 'nop', 'nope'}:
               return False
           retries = retries - 1
           if retries < 0:
               raise ValueError('invalid user response')
           print(reminder)

Questa funzione può essere chiamata in diversi modi:

* dando solo l'argomento obbligatorio: "ask_ok('Vuoi davvero
  uscire?')"

* dando uno degli argomenti opzionali: "ask_ok('OK sovrascrivere il
  file?', 2)"

* o addirittura dando tutti gli argomenti: "ask_ok('OK sovrascrivere
  il file?', 2, 'Dai su, solo sì o no!')"

Questo esempio introduce anche la parola chiave "in". Questo testa se
una sequenza contiene o meno un certo valore.

I valori predefiniti vengono valutati nel punto di definizione della
funzione nello *scope* di definizione, in modo che

   i = 5

   def f(arg=i):
       print(arg)

   i = 6
   f()

stamperà "5".

**Avvertenza importante:** Il valore predefinito viene valutato solo
una volta. Questo fa la differenza quando il valore predefinito è un
oggetto mutabile come una lista, un dizionario, o istanze della
maggior parte delle classi. Ad esempio, la seguente funzione accumula
gli argomenti passati ad essa nelle chiamate successive:

   def f(a, L=[]):
       L.append(a)
       return L

   print(f(1))
   print(f(2))
   print(f(3))

Questo stamperà

   [1]
   [1, 2]
   [1, 2, 3]

Se non vuoi che il valore predefinito sia condiviso tra le chiamate
successive, puoi scrivere la funzione in questo modo:

   def f(a, L=None):
       if L is None:
           L = []
       L.append(a)
       return L


4.8.2. Argomenti chiave-valore
------------------------------

Le funzioni possono anche essere chiamate utilizzando *argomenti
chiave-valore* della forma "kwarg=value". Ad esempio, la seguente
funzione:

   def parrot(voltage, state='a stiff', action='voom', type='Norwegian Blue'):
       print("-- This parrot wouldn't", action, end=' ')
       print("if you put", voltage, "volts through it.")
       print("-- Lovely plumage, the", type)
       print("-- It's", state, "!")

accetta un argomento obbligatorio ("voltage") e tre argomenti
opzionali ("state", "action", e "type"). Questa funzione può essere
chiamata in uno dei seguenti modi:

   parrot(1000)                                          # 1 positional argument
   parrot(voltage=1000)                                  # 1 keyword argument
   parrot(voltage=1000000, action='VOOOOOM')             # 2 keyword arguments
   parrot(action='VOOOOOM', voltage=1000000)             # 2 keyword arguments
   parrot('a million', 'bereft of life', 'jump')         # 3 positional arguments
   parrot('a thousand', state='pushing up the daisies')  # 1 positional, 1 keyword

ma tutte le chiamate seguenti non sarebbero valide:

   parrot()                     # required argument missing
   parrot(voltage=5.0, 'dead')  # non-keyword argument after a keyword argument
   parrot(110, voltage=220)     # duplicate value for the same argument
   parrot(actor='John Cleese')  # unknown keyword argument

In una chiamata di funzione, gli argomenti chiave-valore devono
seguire gli argomenti posizionali. Tutti gli argomenti chiave passati
devono corrispondere a uno degli argomenti accettati dalla funzione
(ad esempio, "actor" non è un argomento valido per la funzione
"parrot"), e il loro ordine non è importante. Questo include anche gli
argomenti non opzionali (ad esempio, "parrot(voltage=1000)" è valido).
Nessun argomento può ricevere un valore più di una volta. Ecco un
esempio che fallisce a causa di questa restrizione:

   >>> def function(a):
   ...     pass
   ...
   >>> function(0, a=0)
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   TypeError: function() got multiple values for argument 'a'

Quando è presente un parametro formale finale della forma "**name",
riceve un dizionario (vedi Tipi di Mapping --- dict) contenente tutti
gli argomenti chiave tranne quelli corrispondenti a un parametro
formale. Questo può essere combinato con un parametro formale della
forma "*name" (descritto nella prossima sottosezione) che riceve una
tupla contenente gli argomenti posizionali oltre all'elenco dei
parametri formali. ("*name" deve occorrere prima di "**name".) Ad
esempio, se definiamo una funzione in questo modo:

   def cheeseshop(kind, *arguments, **keywords):
       print("-- Do you have any", kind, "?")
       print("-- I'm sorry, we're all out of", kind)
       for arg in arguments:
           print(arg)
       print("-" * 40)
       for kw in keywords:
           print(kw, ":", keywords[kw])

Potrebbe essere chiamato in questo modo:

   cheeseshop("Limburger", "It's very runny, sir.",
              "It's really very, VERY runny, sir.",
              shopkeeper="Michael Palin",
              client="John Cleese",
              sketch="Cheese Shop Sketch")

e naturalmente stamperà:

   -- Do you have any Limburger ?
   -- I'm sorry, we're all out of Limburger
   It's very runny, sir.
   It's really very, VERY runny, sir.
   ----------------------------------------
   shopkeeper : Michael Palin
   client : John Cleese
   sketch : Cheese Shop Sketch

Nota che l'ordine in cui gli argomenti chiave-valore vengono stampati
è garantito corrispondere all'ordine in cui sono stati forniti nella
chiamata della funzione.


4.8.3. Parametri speciali
-------------------------

Per impostazione predefinita, gli argomenti possono essere passati a
una funzione Python sia per posizione che esplicitamente per chiave.
Per leggibilità e prestazioni, ha senso limitare il modo in cui gli
argomenti possono essere passati in modo che uno sviluppatore debba
solo guardare la definizione della funzione per determinare se gli
elementi sono passati per posizione, per posizione o per chiave, o per
chiave.

Ecco come potrebbe essere la definizione di una funzione:

   def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
         -----------    ----------     ----------
           |             |                  |
           |        Positional or keyword   |
           |                                - Keyword only
            -- Positional only

dove "/" e "*" sono opzionali. Se utilizzati, questi simboli indicano
il tipo di parametro in base a come gli argomenti possono essere
passati alla funzione: solo per posizione, per posizione o per chiave,
e solo per chiave. I parametri chiave-valore sono anche chiamati
parametri nominali.


4.8.3.1. Argomenti Posizionali o per Chiave
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Se "/" e "*" non sono presenti nella definizione della funzione, gli
argomenti possono essere passati a una funzione per posizione o per
chiave.


4.8.3.2. Parametri Solo per Posizione
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Guardando questo in modo più dettagliato, è possibile contrassegnare
certi parametri come *solo per posizione*. Se lo sono, l'ordine dei
parametri è importante, e i parametri non possono essere passati per
chiave. I parametri solo per posizione sono posizionati prima di un
"/" (barra obliqua). Il "/" è utilizzato per separare logicamente i
parametri posizionali dal resto dei parametri. Se non c'è un "/" nella
definizione della funzione, non ci sono parametri posizionali.

I parametri che seguono il "/" possono essere *posizionali o per
chiave* o *solo per chiave*.


4.8.3.3. Argomenti Solo per Chiave
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Per contrassegnare i parametri come *solo per chiave*, indicando che i
parametri devono essere passati per argomento chiave, posiziona un "*"
nell'elenco degli argomenti subito prima del primo parametro *solo per
chiave*.


4.8.3.4. Esempi di Funzioni
~~~~~~~~~~~~~~~~~~~~~~~~~~~

Considera le seguenti definizioni di funzioni di esempio prestando
particolare attenzione ai marcatori "/" e "*":

   >>> def standard_arg(arg):
   ...     print(arg)
   ...
   >>> def pos_only_arg(arg, /):
   ...     print(arg)
   ...
   >>> def kwd_only_arg(*, arg):
   ...     print(arg)
   ...
   >>> def combined_example(pos_only, /, standard, *, kwd_only):
   ...     print(pos_only, standard, kwd_only)

Nella prima definizione di funzione, "standard_arg", la forma più
familiare, non pone restrizioni sulla convenzione di chiamata e gli
argomenti possono essere passati per posizione o per chiave:

   >>> standard_arg(2)
   2

   >>> standard_arg(arg=2)
   2

La seconda funzione "pos_only_arg" è limitata a utilizzare solo
parametri posizionali poiché c'è un "/" nella definizione della
funzione:

   >>> pos_only_arg(1)
   1

   >>> pos_only_arg(arg=1)
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   TypeError: pos_only_arg() got some positional-only arguments passed as keyword arguments: 'arg'

La terza funzione "kwd_only_args" consente solo argomenti chiave-
valore come indicato da un "*" nella definizione della funzione:

   >>> kwd_only_arg(3)
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   TypeError: kwd_only_arg() takes 0 positional arguments but 1 was given

   >>> kwd_only_arg(arg=3)
   3

E l'ultimo utilizza tutte e tre le convenzioni di chiamata nella
stessa definizione di funzione:

   >>> combined_example(1, 2, 3)
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   TypeError: combined_example() takes 2 positional arguments but 3 were given

   >>> combined_example(1, 2, kwd_only=3)
   1 2 3

   >>> combined_example(1, standard=2, kwd_only=3)
   1 2 3

   >>> combined_example(pos_only=1, standard=2, kwd_only=3)
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   TypeError: combined_example() got some positional-only arguments passed as keyword arguments: 'pos_only'

Infine, considera questa definizione di funzione che ha un potenziale
conflitto tra l'argomento posizionale "name" e "**kwds" che ha "name"
come chiave:

   def foo(name, **kwds):
       return 'name' in kwds

Non esiste una chiamata possibile che lo farà restituire "True" poiché
la chiave "'name'" si legherà sempre al primo parametro. Ad esempio:

   >>> foo(1, **{'name': 2})
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   TypeError: foo() got multiple values for argument 'name'
   >>>

Ma utilizzando "/" (argomenti solo per posizione), è possibile poiché
consente "name" come argomento posizionale e "'name'" come chiave
negli argomenti chiave-valore:

   >>> def foo(name, /, **kwds):
   ...     return 'name' in kwds
   ...
   >>> foo(1, **{'name': 2})
   True

In altre parole, i nomi dei parametri solo per posizione possono
essere utilizzati in "**kwds" senza ambiguità.


4.8.3.5. Riepilogo
~~~~~~~~~~~~~~~~~~

Il caso d'uso determinerà quali parametri utilizzare nella definizione
della funzione:

   def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):

Come guida:

* Usa solo argomenti per posizione se vuoi che il nome dei parametri
  non sia disponibile per l'utente. Questo è utile quando i nomi dei
  parametri non hanno un vero significato, se vuoi imporre l'ordine
  degli argomenti quando la funzione viene chiamata o se devi prendere
  alcuni parametri posizionali e argomenti chiave arbitrari.

* Usa solo argomenti per chiave quando i nomi hanno significato e la
  definizione della funzione è più comprensibile essendo esplicita con
  i nomi o vuoi impedire agli utenti di fare affidamento sulla
  posizione dell'argomento passato.

* Per un'API, usa solo per posizione per evitare rotture delle
  modifiche API se il nome del parametro viene modificato in futuro.


4.8.4. Liste di Argomenti Arbitrarie
------------------------------------

Infine, l'opzione meno utilizzata è specificare che una funzione può
essere chiamata con un numero arbitrario di argomenti. Questi
argomenti saranno racchiusi in una tupla (vedi Tuple e Sequenze).
Prima del numero variabile di argomenti, possono verificarsi zero o
più argomenti normali.

   def write_multiple_items(file, separator, *args):
       file.write(separator.join(args))

Normalmente, questi argomenti *variadici* saranno gli ultimi
nell'elenco dei parametri formali, perché raccolgono tutti gli
argomenti di input rimanenti che vengono passati alla funzione.
Qualsiasi parametro formale che si verifica dopo il parametro "*args"
sono argomenti 'solo per chiave', il che significa che possono essere
utilizzati solo come argomenti chiave piuttosto che argomenti
posizionali.

   >>> def concat(*args, sep="/"):
   ...     return sep.join(args)
   ...
   >>> concat("earth", "mars", "venus")
   'earth/mars/venus'
   >>> concat("earth", "mars", "venus", sep=".")
   'earth.mars.venus'


4.8.5. Spacchettamento delle Liste di Argomenti
-----------------------------------------------

La situazione inversa si verifica quando gli argomenti sono già in una
lista o tupla ma devono essere spacchettati per una chiamata di
funzione che richiede argomenti posizionali separati. Ad esempio, la
funzione built-in "range()" si aspetta argomenti *start* e *stop*
separati. Se non sono disponibili separatamente, scrivi la chiamata di
funzione con l'operatore "*"per spacchettare gli argomenti da una
lista o tupla:

   >>> list(range(3, 6))            # normal call with separate arguments
   [3, 4, 5]
   >>> args = [3, 6]
   >>> list(range(*args))            # call with arguments unpacked from a list
   [3, 4, 5]

Nello stesso modo, i dizionari possono fornire argomenti chiave-valore
con l'operatore "**"::

   >>> def parrot(voltage, state='a stiff', action='voom'):
   ...     print("-- This parrot wouldn't", action, end=' ')
   ...     print("if you put", voltage, "volts through it.", end=' ')
   ...     print("E's", state, "!")
   ...
   >>> d = {"voltage": "four million", "state": "bleedin' demised", "action": "VOOM"}
   >>> parrot(**d)
   -- This parrot wouldn't VOOM if you put four million volts through it. E's bleedin' demised !


4.8.6. Espressioni Lambda
-------------------------

Piccole funzioni anonime possono essere create con la parola chiave
"lambda". Questa funzione restituisce la somma dei suoi due argomenti:
"lambda a, b: a+b". Le funzioni lambda possono essere utilizzate
ovunque siano richiesti oggetti funzione. Sono sintatticamente
limitate a una singola espressione. Semanticamente, sono solo zucchero
sintattico per una normale definizione di funzione. Come le
definizioni di funzione nidificate, le funzioni lambda possono fare
riferimento a variabili dallo scope contenente:

   >>> def make_incrementor(n):
   ...     return lambda x: x + n
   ...
   >>> f = make_incrementor(42)
   >>> f(0)
   42
   >>> f(1)
   43

Nell'esempio precedente viene utilizzata un'espressione lambda per
restituire una funzione. Un altro uso è passare una piccola funzione
come argomento:

   >>> pairs = [(1, 'one'), (2, 'two'), (3, 'three'), (4, 'four')]
   >>> pairs.sort(key=lambda pair: pair[1])
   >>> pairs
   [(4, 'four'), (1, 'one'), (3, 'three'), (2, 'two')]


4.8.7. Stringhe di Documentazione
---------------------------------

Ecco alcune convenzioni sul contenuto e la formattazione delle
stringhe di documentazione.

La prima riga dovrebbe essere sempre un breve e conciso riassunto
dello scopo dell'oggetto. Per brevità, non dovrebbe esplicitamente
indicare il nome o il tipo dell'oggetto, poiché questi sono
disponibili in altri modi (tranne se il nome è un verbo che descrive
l'operazione di una funzione). Questa riga dovrebbe iniziare con una
lettera maiuscola e terminare con un punto.

Se ci sono più righe nella stringa di documentazione, la seconda riga
dovrebbe essere vuota, separando visivamente il riassunto dal resto
della descrizione. Le righe seguenti dovrebbero essere uno o più
paragrafi che descrivono le convenzioni di chiamata dell'oggetto, i
suoi effetti collaterali, ecc.

Il parser di Python non rimuove l'indentazione dalle stringhe
letterali multilinea in Python, quindi gli strumenti che elaborano la
documentazione devono rimuovere l'indentazione se desiderato. Questo
viene fatto utilizzando la seguente convenzione. La prima riga non
vuota *dopo* la prima riga della stringa determina la quantità di
indentazione per l'intera stringa di documentazione. (Non possiamo
usare la prima riga poiché è generalmente adiacente alle virgolette di
apertura della stringa quindi la sua indentazione non è evidente nella
stringa letterale.) Lo spazio bianco "equivalente" a questa
indentazione viene quindi rimosso dall'inizio di tutte le righe della
stringa. Le righe che sono indentate di meno non dovrebbero
verificarsi, ma se si verificano tutto il loro spazio bianco iniziale
dovrebbe essere rimosso. L'equivalenza dello spazio bianco dovrebbe
essere testata dopo l'espansione delle tabulazioni (a 8 spazi,
normalmente).

Di seguito è riportato un esempio di una stringa di documentazione
multilinea:

   >>> def my_function():
   ...     """Do nothing, but document it.
   ...
   ...     No, really, it doesn't do anything.
   ...     """
   ...     pass
   ...
   >>> print(my_function.__doc__)
   Do nothing, but document it.

       No, really, it doesn't do anything.


4.8.8. Annotationi delle Funzioni
---------------------------------

Le annotationi delle funzioni sono informazioni di metadati
completamente opzionali sui tipi utilizzati dalle funzioni definite
dall'utente (vedi **PEP 3107** e **PEP 484** per ulteriori
informazioni).

*Le annotazioni* sono memorizzate nell'attributo "__annotations__"
della funzione come un dizionario e non hanno alcun effetto su
qualsiasi altra parte della funzione. Le annotazioni dei parametri
sono definite da due punti dopo il nome del parametro, seguito da
un'espressione che valuta il valore dell'annotazione. Le annotazioni
di ritorno sono definite da una freccia "->", seguita da
un'espressione, tra l'elenco dei parametri e i due punti che indicano
la fine della dichiarazione "def". L'esempio seguente ha un argomento
obbligatorio, un argomento opzionale, e il valore di ritorno annotato:

   >>> def f(ham: str, eggs: str = 'eggs') -> str:
   ...     print("Annotations:", f.__annotations__)
   ...     print("Arguments:", ham, eggs)
   ...     return ham + ' and ' + eggs
   ...
   >>> f('spam')
   Annotations: {'ham': <class 'str'>, 'return': <class 'str'>, 'eggs': <class 'str'>}
   Arguments: spam eggs
   'spam and eggs'


4.9. Intermezzo: Stile di Codifica
==================================

Ora che stai per scrivere pezzi di Python più lunghi e complessi, è un
buon momento per parlare dello *stile di codifica*. La maggior parte
dei linguaggi può essere scritta (o più concisamente, *formattata*) in
stili diversi; alcuni sono più leggibili di altri. Rendere facile per
gli altri leggere il tuo codice è sempre una buona idea, e adottare
uno stile di codifica piacevole aiuta enormemente.

Per Python, la **PEP 8** è la guida di stile a cui aderiscono la
maggior parte dei progetti; promuove uno stile di codifica molto
leggibile e piacevole alla vista. Ogni sviluppatore Python dovrebbe
leggerla prima o poi; ecco i punti più importanti estratti per te:

* Usa l'indentazione a 4 spazi, e niente tabulazioni.

  4 spazi sono un buon compromesso tra una piccola indentazione
  (permette una maggiore profondità di nidificazione) e una grande
  indentazione (più facile da leggere). Le tabulazioni introducono
  confusione, ed è preferibile non usarle.

* Manda a capo le righe in modo che non superino i 79 caratteri.

  Questo aiuta gli utenti con schermi piccoli e rende possibile avere
  diversi file di codice affiancati su schermi più grandi.

* Usa righe vuote per separare funzioni e classi, e blocchi di codice
  più grandi all'interno delle funzioni.

* Quando possibile, metti i commenti su una riga a parte.

* Usa le stringhe di documentazione.

* Usa spazi attorno agli operatori e dopo le virgole, ma non
  direttamente all'interno dei costrutti tra parentesi: "a = f(1, 2) +
  g(3, 4)".

* Nomina le tue classi e funzioni in modo coerente; la convenzione è
  utilizzare "UpperCamelCase" per le classi e
  "lowercase_with_underscores" per le funzioni e i metodi. Usa sempre
  "self" come nome per il primo argomento del metodo (vedi Una prima
  occhiata alle classi per ulteriori informazioni su classi e metodi).

* Non utilizzare codifiche complesse se il tuo codice è destinato ad
  essere utilizzato in ambienti internazionali. Il predefinito di
  Python, UTF-8, o anche il semplice ASCII funzionano meglio in ogni
  caso.

* Allo stesso modo, non utilizzare caratteri non-ASCII negli
  identificatori se c'è solo la minima possibilità che persone che
  parlano una lingua diversa leggano o mantengano il codice.

-[ Note a piè di pagina ]-

[1] In realtà, *chiamata per riferimento all'oggetto* sarebbe una
    descrizione migliore, poiché se viene passato un oggetto mutabile,
    il chiamante vedrà qualsiasi modifica che il chiamato fa ad esso
    (elementi inseriti in una lista).
