# 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. `break` and `continue` Statements

The `break` statement breaks out of the innermost enclosing `for` or `while` loop:

    >>> for n in range(2, 10):
    ...     for x in range(2, n):
    ...         if n % x == 0:
    ...             print(f"{n} equals {x} * {n//x}")
    ...             break
    ...
    4 equals 2 * 2
    6 equals 2 * 3
    8 equals 2 * 4
    9 equals 3 * 3

The `continue` statement continues with the next iteration of the loop:

    >>> for num in range(2, 10):
    ...     if num % 2 == 0:
    ...         print(f"Found an even number {num}")
    ...         continue
    ...     print(f"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. `else` Clauses on Loops

In a `for` or `while` loop the `break` statement may be paired with an `else` clause. If the loop finishes without executing the `break`, the `else` clause executes.

In a `for` loop, the `else` clause is executed after the loop finishes its final iteration, that is, if no break occurred.

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

In either kind of loop, the `else` clause is not executed if the loop was terminated by a `break`. Of course, other ways of ending the loop early, such as a `return` or a raised exception, will also skip execution of the `else` clause.

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

(Yes, this is the correct code. Look closely: the `else` clause belongs to the `for` loop, not the `if` statement.)

One way to think of the else clause is to imagine it paired with the `if` inside the loop. As the loop executes, it will run a sequence like if/if/if/else. The `if` is inside the loop, encountered a number of times. If the condition is ever true, a `break` will happen. If the condition is never true, the `else` clause outside the loop will execute.

When used with a loop, the `else` clause has more in common with the `else` clause of a `try` statement than it does with that of `if` statements: a `try` statement’s `else` clause runs when no exception occurs, and a loop’s `else` clause runs when no `break` occurs. For more on the `try` statement and exceptions, see Gestione delle Eccezioni.

## 4.6. 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.7. 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.8. Definizione di Funzioni

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

    >>> def fib(n):    # write Fibonacci series less than n
    ...     """Print a Fibonacci series less than 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.9. Ulteriori informazioni sulla Definizione di Funzioni

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

### 4.9.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.9.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.9.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.9.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.9.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.9.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.9.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'

The third function `kwd_only_arg` only allows keyword arguments as indicated by a `*` in the function definition:

    >>> 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.9.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.9.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.9.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.9.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.9.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.9.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.10. 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).