Chapter 5 Istruzioni condizionali e ricorsioneL’argomento principale di questo capitolo è l’istruzione if, che permette di eseguire codice diverso a seconda dello stato del programma. Prima di tutto, vediamo però due nuovi operatori: divisione intera e modulo. 5.1 Divisione intera e moduloL’operatore di divisione intera, >>> minuti = 105 >>> minuti / 60 1.75 Ma di solito non si esprimono le ore con un numero decimale. La divisione intera dà invece come risultato il numero di ore e tralascia la frazione di ora: >>> minuti = 105 >>> ore = minuti // 60 >>> ore 1 Per ottenere il resto, potete sottrarre dai minuti l’equivalente delle ore: >>> resto = minuti - ore * 60 >>> resto 45 Un’alternativa è utilizzare l’operatore modulo, >>> resto = minuti % 60 >>> resto 45 L’operatore modulo è più utile di quel che sembra. Per esempio, permette di controllare se un numero intero è divisibile per un altro: se x % y è zero, significa che x è divisibile per y. Inoltre, può essere usato per estrarre la cifra più a destra di un numero: x % 10 restituisce la cifra più a destra del numero x (in base 10). Allo stesso modo, x % 100 restituisce le ultime due cifre. Per chi usa Python 2, la divisione funziona in modo diverso. L’operatore di divisione intera non esiste, e quello di divisione, 5.2 Espressioni booleaneUn’espressione booleana è un’espressione che può essere o vera o falsa. Gli esempi che seguono usano l’operatore ==, confrontano due valori e restituiscono True (vero) se sono uguali, False (falso) altrimenti: >>> 5 == 5 True >>> 5 == 6 False True e False sono valori speciali che sono di tipo bool; non sono delle stringhe: >>> type(True) <class 'bool'> >>> type(False) <class 'bool'> L’operatore == è uno degli operatori di confronto; gli altri sono: x != y # x è diverso da y x > y # x è maggiore di y x < y # x è minore di y x >= y # x è maggiore o uguale a y x <= y # x è minore o uguale a y Queste operazioni vi saranno familiari, tuttavia i simboli in Python sono diversi da quelli usati comunemente in matematica. Un errore frequente è quello di usare il simbolo di uguale(=) invece del doppio uguale (==). Ricordate che = è un operatore di assegnazione, mentre == è un operatore di confronto. Inoltre in Python non esistono simboli del tipo =< o =>. 5.3 Operatori logiciCi sono tre operatori logici: and, or, e not. Il significato di questi operatori è simile al loro significato comune (e, o, non): per esempio, l’espressione x > 0 and x < 10 è vera solo se sono vere entrambe le condizioni, cioè x è più grande di 0 e più piccolo di 10. L’espressione n%2 == 0 or n%3 == 0 invece è vera se si verifica almeno una delle due condizioni e cioè se il numero è divisibile per 2 o per 3 (o per entrambi). Infine, l’operatore not nega il valore di un’espressione booleana, trasformando in falsa un’espressione vera e viceversa. Così, not (x > y) è vera se x > y è falsa, cioè se x è minore o uguale a y. In senso stretto, gli operandi degli operatori logici dovrebbero essere delle espressioni booleane, ma da questo punto di vista Python non è troppo fiscale: infatti ogni numero diverso da zero viene considerato True, e lo zero è considerato False. >>> 42 and True True Questa flessibilità può essere utile, ma ci sono alcune sottigliezze che potrebbero confondere. È preferibile evitarla (a meno che non sappiate quello che state facendo). 5.4 Esecuzione condizionalePer poter scrivere programmi utili, c’è molto spesso la necessità di valutare delle condizioni e di variare il comportamento del programma a seconda del risultato della valutazione. Le istruzioni condizionali ci offrono questa possibilità. La forma più semplice è l’istruzione if (“se” in inglese): if x > 0: print("x è positivo") L’espressione booleana dopo l’istruzione if è chiamata condizione. L’istruzione indentata che segue i due punti della riga if, viene eseguita solo se la condizione è vera. Se la condizione è falsa non viene eseguito nulla. Come nel caso della definizione di funzione, la struttura dell’istruzione if è costituita da un’intestazione seguita da un corpo indentato. Le istruzioni come questa vengono chiamate istruzioni composte. Non c’è limite al numero di istruzioni che possono comparire nel corpo, ma deve sempre essercene almeno una. In qualche occasione può essere utile avere un corpo vuoto, ad esempio quando il codice corrispondente non è ancora stato scritto ma si desidera ugualmente provare il programma. In questo caso si può usare l’istruzione pass, che serve solo da segnaposto temporaneo e nulla più: if x < 0: pass # scrivere cosa fare con i valori negativi! 5.5 Esecuzione alternativaUna seconda forma di istruzione if è l’esecuzione alternativa, nella quale ci sono due azioni possibili, e il valore della condizione determina quale delle due debba essere eseguita e quale no. La sintassi è: if x % 2 == 0: print("x è pari") else: print("x è dispari") Se il resto della divisione di x per 2 è zero, significa che x è un numero pari, e il programma mostra il messaggio appropriato. Se invece la condizione è falsa, viene eseguita la serie di istruzioni descritta dopo la riga else (che in inglese significa “altrimenti”). In ogni caso, una delle due alternative sarà sempre eseguita. Le due alternative sono chiamate ramificazioni, perché rappresentano dei bivi nel flusso di esecuzione del programma. 5.6 Condizioni in serieTalvolta occorre tenere conto di più di due possibili sviluppi, pertanto possiamo aver bisogno di più di due ramificazioni. Un modo per esprimere questo tipo di calcolo sono le condizioni in serie: if x < y: print("x è minore di y") elif x > y: print("x è maggiore di y") else: print("x e y sono uguali") elif è l’abbreviazione di else if, che in inglese significa “altrimenti se”. Anche in questo caso, solo uno dei rami verrà eseguito, a seconda dell’esito del confronto tra x e y. Non c’è alcun limite al numero di istruzioni elif. Se esiste una clausola else, deve essere scritta per ultima, ma non è obbligatoria; il ramo corrispondente viene eseguito solo quando tutte le condizioni precedenti sono false. if scelta == 'a': disegna_a() elif scelta == 'b': disegna_b() elif scelta == 'c': disegna_c() Le condizioni vengono controllate nell’ordine in cui sono state scritte: se la prima è falsa viene controllata la seconda e così via. Non appena una condizione risulta vera, viene eseguito il ramo corrispondente e l’intera istruzione if si conclude. In ogni caso, anche se risultassero vere altre condizioni successive, dopo l’esecuzione della prima queste ultime verranno trascurate. 5.7 Condizioni nidificateSi può anche inserire un’istruzione condizionale nel corpo di un’altra istruzione condizionale. Possiamo scrivere l’esempio del paragrafo precedente anche in questo modo: if x == y: print("x e y sono uguali") else: if x < y: print("x è minore di y") else: print("x è maggiore di y") La prima condizione esterna (if x == y) contiene due rami: il primo contiene un’istruzione semplice, il secondo un’altra istruzione if che a sua volta prevede un’ulteriore ramificazione. Entrambi i rami del secondo if sono istruzioni di stampa, ma potrebbero anche contenere a loro volta ulteriori istruzioni condizionali. Sebbene l’indentazione delle istruzioni aiuti a rendere evidente la struttura, le condizioni nidificate diventano rapidamente difficili da leggere, quindi è meglio usarle con moderazione. Qualche volta, gli operatori logici permettono di semplificare le istruzioni condizionali nidificate: if 0 < x: if x < 10: print("x è un numero positivo a una cifra.") L’istruzione di stampa è eseguita solo se entrambe le condizioni si verificano. Possiamo allora usare l’operatore booleano and per combinarle: if 0 < x and x < 10: print("x è un numero positivo a una cifra.") Per una condizione di questo tipo, Python consente anche un’opzione sintattica più concisa: if 0 < x < 10: print("x è un numero positivo a una cifra.") 5.8 RicorsioneAbbiamo visto che è del tutto normale che una funzione ne chiami un’altra, ma è anche consentito ad una funzione di chiamare se stessa. L’utilità può non essere immediatamente evidente, ma questa è una delle cose più magiche che un programma possa fare. Per fare un esempio, diamo un’occhiata a questa funzione: def contoallarovescia(n): if n <= 0: print("Via!") else: print(n) contoallarovescia(n-1) Se n vale 0 o è negativo, scrive la parola “Via!”. Altrimenti scrive il numero n e poi chiama la funzione contoallarovescia (cioè se stessa) passando un argomento che vale n-1. Cosa succede quando chiamiamo la funzione in questo modo? >>> contoallarovescia(3) L’esecuzione di contoallarovescia inizia da n=3, e dato che n è maggiore di 0, stampa il valore 3, poi chiama se stessa... L’esecuzione di contoallarovescia inizia da n=2, e dato che n è maggiore di 0, stampa il valore 2, poi chiama se stessa...L’esecuzione di contoallarovescia inizia da n=1, e dato che n è maggiore di 0, stampa il valore 1, poi chiama se stessa...L’esecuzione di contoallarovescia inizia da n=0, e dato che n è uguale a 0, stampa la parola “Via!” e poi ritorna. La funzione contoallarovescia che aveva dato n=3 ritorna. E infine ritorniamo in 3 2 1 Via! Una funzione che chiama se stessa si dice ricorsiva e la procedura che la esegue è detta ricorsione. Come secondo esempio, scriviamo una funzione che stampi una data stringa per n volte. def stampa_n(s, n): if n <= 0: return print(s) stampa_n(s, n-1) Se n <= 0 l’istruzione di ritorno return provoca l’uscita dalla funzione. Il flusso dell’esecuzione torna immediatamente al chiamante, e le righe rimanenti della funzione non vengono eseguite. Il resto della funzione è simile a contoallarovescia: visualizza la stringa s e chiama se stessa per n−1 altre volte. Il numero di righe risultanti sarà 1 + (n - 1), che corrisponde a n. Per esempi semplici come questi, è forse più facile usare un ciclo for. Vedremo però più avanti degli esempi difficili da scrivere con un ciclo for ma facili con la ricorsione; meglio quindi cominciare subito a prendere mano. 5.9 Diagrammi di stack delle funzioni ricorsiveNel Paragrafo 3.9, abbiamo usato un diagramma di stack per rappresentare lo stato di un programma durante una chiamata di funzione. Lo stesso tipo di diagramma può servire a capire come lavora una funzione ricorsiva. Ogni volta che una funzione viene chiamata, Python crea un nuovo frame contenente le variabili locali definite all’interno della funzione ed i suoi parametri. Nel caso di una funzione ricorsiva, possono esserci contemporaneamente più frame riguardanti una stessa funzione. La Figura 5.1 mostra il diagramma di stack della funzione contoallarovescia chiamata con n = 3. Come al solito, il livello superiore dello stack è il frame di I quattro frame di contoallarovescia hanno valori diversi del parametro n. Il livello inferiore dello stack, dove n=0, è chiamato caso base. Esso non effettua ulteriori chiamate ricorsive, quindi non ci sono ulteriori frame. Come esercizio, disegnate il diagramma di stack della funzione 5.10 Ricorsione infinitaSe una ricorsione non raggiunge mai un caso base, la chiamata alla funzione viene eseguita all’infinito ed in teoria il programma non giunge mai alla fine. Questa situazione è conosciuta come ricorsione infinita, e di solito non è considerata una buona cosa. Questo è un programma minimo che genera una ricorsione infinita: def ricorsiva(): ricorsiva() Nella maggior parte degli ambienti, un programma con una ricorsione infinita non viene eseguito davvero all’infinito. Python stampa un messaggio di errore quando è stato raggiunto il massimo livello di ricorsione possibile: File "<stdin>", line 2, in ricorsiva File "<stdin>", line 2, in ricorsiva File "<stdin>", line 2, in ricorsiva . . . File "<stdin>", line 2, in ricorsiva RuntimeError: Maximum recursion depth exceeded Questo traceback è un po’ più lungo di quello che abbiamo visto nel capitolo precedente. Quando si verifica l’errore, nello stack ci sono oltre 1000 frame di chiamata di ricorsiva! Se vi imbattete accidentalmente in una ricorsione infinita, rivedete la vostra funzione per accertare che esista un caso base che non genera una chiamata ricorsiva. E se c’è un caso base, controllate che venga sicuramente raggiunto. 5.11 Input da tastieraI programmi che abbiamo scritto finora non accettano inserimenti di dati da parte dell’operatore, e si limitano a eseguire sempre le stesse operazioni. Python comprende una funzione predefinita chiamata >>> testo = input() Cosa stai aspettando? >>> testo 'Cosa stai aspettando?' Prima di chiamare la funzione, è buona norma stampare un messaggio che informa l’utente di ciò che deve inserire. Questo messaggio è chiamato prompt, e può essere passato come argomento a >>> nome = input('Come...ti chiami?\n') Come...ti chiami? Artu', Re dei Bretoni! >>> nome 'Artu', Re dei Bretoni!' La sequenza Se il valore da inserire è un intero possiamo provare a convertire il valore inserito in int: >>> prompt = "Qual è la velocità in volo di una rondine?\n" >>> velocita = input(prompt) Qual è la velocità in volo di una rondine? 42 >>> int(velocita) 42 Ma se la stringa inserita contiene qualcosa di diverso da dei numeri, si verifica un errore: >>> velocita = input(prompt) Qual è la velocità in volo di una rondine? Cosa intendi, una rondine europea o africana? >>> int(velocita) ValueError: invalid literal for int() with base 10 Vedremo più avanti come trattare questo tipo di errori. 5.12 DebugQuando si verifica un errore di sintassi o di runtime, il messaggio d’errore contiene molte informazioni, ma può essere sovrabbondante. Di solito le parti più utili sono:
Gli errori di sintassi di solito sono facili da trovare, con qualche eccezione. Gli spaziatori possono essere insidiosi, perché spazi e tabulazioni non sono visibili e siamo abituati a non tenerne conto. >>> x = 5 >>> y = 6 File "<stdin>", line 1 y = 6 ^ IndentationError: unexpected indent In questo esempio, il problema è che la seconda riga è erroneamente indentata di uno spazio, mentre dovrebbe stare al margine sinistro. Ma il messaggio di errore punta su y, portando fuori strada. In genere, i messaggi di errore indicano dove il problema è venuto a galla, ma il vero errore potrebbe essere in un punto precedente del codice, a volte anche nella riga precedente. Lo stesso vale per gli errori di runtime. Supponiamo di voler calcolare un rapporto segnale/rumore in decibel. La formula è SNRdb = 10 log10 (Psegnale / Prumore). In Python si può scrivere: import math potenza_segnale = 9 potenza_rumore = 10 rapporto = potenza_segnale // potenza_rumore decibel = 10 * math.log10(rapporto) print(decibel) Se avviate questo programma, compare un messaggio di errore. Traceback (most recent call last): File "snr.py", line 5, in ? decibel = 10 * math.log10(rapporto) ValueError: math domain error Il messaggio punta alla riga 5, ma lì non c’è niente di sbagliato. Per trovare il vero errore, può essere utile stampare il valore di rapporto, che risulta essere 0. Il problema sta nella riga 4, perché calcola una divisione intera anziché una normale divisione. Prendetevi la briga di leggere attentamente i messaggi di errore, ma non date per scontato che tutto quello che dicono sia esatto. 5.13 Glossario
5.14 EserciziEsercizio 1 Il modulo time contiene una funzione, anch’essa di nome time, che restituisce l’attuale GMT (Tempo Medio di Greenwich) riferito ad un “tempo zero”, che è un momento arbitrario usato come punto di riferimento. Nei sistemi UNIX, questo “tempo zero” è il 1 gennaio 1970. >>> import time >>> time.time() 1437746094.5735958 Realizzate uno script che acquisisca il tempo attuale e lo converta in un tempo in ore, minuti e secondi, più i giorni trascorsi dal “tempo zero”.
Esercizio 2
L’ultimo teorema di Fermat afferma che non esistono interi positivi a, b, e c tali che
per qualsiasi valore di n maggiore di 2.
Esercizio 3
Dati tre bastoncini, può essere possibile o meno riuscire a sistemarli in modo da formare un triangolo. Per esempio, se uno dei bastoncini misura 12 centimetri e gli altri due 1 centimetro, non riuscirete a far toccare le estremità di tutti e tre i bastoncini. Date tre lunghezze, c’è una semplice regola per controllare se è possibile formare un triangolo: Se una qualsiasi delle tre lunghezze è maggiore della somma delle altre due, non potete formare un triangolo. (Se la somma di due lunghezze è uguale alla terza, si ha un triangolo “degenere”.)
Esercizio 4 Qual è l’output del seguente programma? Disegnate un diagramma di stack che illustri lo stato del programma nel momento in cui stampa il risultato. def ricorsione(n, s): if n == 0: print(s) else: ricorsione(n-1, n+s) ricorsione(3, 0)
Gli esercizi seguenti utilizzano il modulo turtle, descritto nel Capitolo 4:
Esercizio 5 Leggete la seguente funzione e cercate di capire cosa fa (vedere gli esempi nel Capitolo 4). Quindi eseguitela per controllare se avevate indovinato. def disegna(t, lunghezza, n): if n == 0: return angolo = 50 t.fd(lunghezza*n) t.lt(angolo) disegna(t, lunghezza, n-1) t.rt(2*angolo) disegna(t, lunghezza, n-1) t.lt(angolo) t.bk(lunghezza*n)
Esercizio 6
La curva di Koch è un frattale che somiglia a quello in Figura 5.2. Per disegnare una curva di Koch di lunghezza x, dovete:
Ad eccezione di quando x è minore di 3: in questo caso si disegna una linea dritta lunga x.
|
ContributeIf you would like to make a contribution to support my books, you can use the button below. Thank you!
Are you using one of our books in a class?We'd like to know about it. Please consider filling out this short survey.
|