Next Up Previous Hi Index

Chapter 4

Istruzioni condizionali e ricorsione

4.1 L'operatore modulo

L'operatore modulo opera sugli interi (e sulle espressioni intere) e produce il resto della divisione del primo operando diviso per il secondo. In Python l'operatore modulo è rappresentato dal segno percentuale (%). La sintassi è la stessa degli altri operatori matematici:

>>> Quoziente = 7 / 3
>>> print Quoziente
2
>>> Resto = 7 % 3
>>> print Resto
1

Così 7 diviso 3 dà 2, con il resto di 1.

L'operatore modulo è molto utile in quanto ti permette di controllare se un numero è divisibile per un altro: se x % y è 0, allora 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 in base 10. Allo stesso modo x%100 restituisce le ultime due cifre.

4.2 Espressioni booleane

Un'espressione booleana è un'espressione che è o vera o falsa. In Python un'espressione che è vera ha valore 1, un'espressione falsa ha valore 0.

L'operatore == confronta due valori e produce un risultato di tipo booleano:

>>> 5 == 5
1
>>> 5 == 6
0

Nella prima riga i due operandi sono uguali, così l'espressione vale 1 (vero); nella seconda riga 5 e 6 non sono uguali, così otteniamo 0 (falso).

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?

Sebbene queste operazioni ti possano sembrare familiari, i simboli Python sono diversi da quelli usati comunemente in matematica. \ Un errore comune è quello di usare il simbolo di uguale (=) invece del doppio uguale (==): ricorda che = è un operatore di assegnazione e == un operatore di confronto. Inoltre in Python non esistono simboli del tipo =< e =>, ma solo gli equivalenti <= e >=.

4.3 Operatori logici

Ci sono tre operatori logici: and, or e not. Il significato di questi operatori è simile al loro significato in italiano: per esempio, (x>0) and (x<10) è vera solo se x è più grande di 0 e meno di 10.

(n%2==0) or (n%3==0) è vera se si verifica almeno una delle due condizioni e cioè se il numero è divisibile per 2 o per 3.

Infine, l'operatore not nega il valore di un'espressione booleana, trasformando in falsa un'espressione vera e viceversa. Così se x>y è vera (x è maggiore di y), not(x>y) è falsa.

A dire il vero gli operatori booleani dovrebbero restituire un valore vero o falso, ma da questo punto di vista Python (come parte dei linguaggi di programmazione) non sembra essere troppo fiscale: infatti ogni valore diverso da zero viene considerato vero e lo zero è considerato falso.

>>>  x = 5
>>>  x and 1
1
>>>  y = 0
>>>  y and 1
0

In generale, le righe appena viste pur essendo lecite non sono considerate un buon esempio di programmazione: se vuoi confrontare un valore con zero è sempre meglio farlo in modo esplicito, con un'espressione del tipo

>>>  x != 0

4.4 Esecuzione condizionale

Per poter scrivere programmi di una certa utilità dobbiamo essere messi in grado di valutare delle condizioni e di far seguire differenti percorsi al flusso di esecuzione a seconda del risultato della valutazione. Le istruzioni condizionali ci offrono questa possibilità. La forma più semplice di istruzione if è la seguente:

if x > 0:
  print "x e' 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 alcunché.

Come nel caso di altre istruzioni composte, l'istruzione if è costituita da un'intestazione e da un blocco di istruzioni:

INTESTAZIONE:
  PRIMA RIGA DI ISTRUZIONI
  ...
  ULTIMA RIGA DI ISTRUZIONI

L'intestazione inizia su di una nuova riga e termina con il segno di due punti. La serie di istruzioni indentate che seguono sono chiamate blocco di istruzioni. La prima riga di istruzioni non indentata marca la fine del blocco di istruzioni e non ne fa parte. Un blocco di istruzioni all'interno di un'istruzione composta è anche chiamato corpo dell'istruzione.

Non c'è un limite al numero di istruzioni che possono comparire nel corpo di un'istruzione if 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 poter provare il programma. In questo caso puoi usare l'istruzione pass, che è solo un segnaposto e non fa niente:

if x > 0:
  pass

4.5 Esecuzione alternativa

Una seconda forma di istruzione if è l'esecuzione alternativa, nella quale ci sono due possibilità di azione e il valore della condizione determina quale delle due debba essere scelta. La sintassi è:

if x%2 == 0:
  print x, "e' pari"
else:
  print x, "e' dispari"

Se il resto della divisione intera di x per 2 è zero allora sappiamo che x è pari e il programma mostra il messaggio corrispondente. Se la condizione è falsa viene eseguita la serie di istruzioni descritta dopo la riga else (che in inglese significa "altrimenti").

Le due alternative sono chiamate ramificazioni perché rappresentano delle ramificazioni nel flusso di esecuzione del programma, e solo una di esse verrà effettivamente eseguita.

Una nota: se hai bisogno di controllare la parità di un numero (vedere se il numero è pari o dispari), potresti desiderare di creare una funzione apposita da poter riutilizzare in seguito:

def StampaParita(x):
  if x%2 == 0:
    print x, "e' pari"
  else:
    print x, "e' dispari"

Così per ogni valore intero di x, StampaParita mostra il messaggio appropriato. Quando chiami questa funzione puoi fornire qualsiasi espressione intera come argomento.

>>> StampaParita(17)
>>> StampaParita(y+1)

4.6 Condizioni in serie

Talvolta ci sono più di due possibilità per la continuazione del programma, così possiamo aver bisogno di più di due ramificazioni. Un modo per esprimere questo caso sono le condizioni in serie:

if x < y:
  print x, "e' minore di", y
elif x > y:
  print x, "e' 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 del confronto tra x e y. Non c'è alcun limite al numero di istruzioni elif ma è eventualmente possibile inserire un'unica istruzione else che deve essere l'ultima dell'elenco e che rappresenta l'azione da eseguire quando nessuna delle condizioni precedenti è stata soddisfatta. La presenza di un'istruzione else è facoltativa.

if scelta == 'A':
  FunzioneA()
elif scelta == 'B':
  FunzioneB()
elif scelta == 'C':
  FunzioneC()
else:
  print "Scelta non valida"

Le condizioni sono controllate nell'ordine in cui sono state scritte. Se la prima è falsa viene provata la seconda e così via. Non appena una è verificata viene eseguito il ramo corrispondente e l'intera istruzione if viene conclusa. In ogni caso, anche se fossero vere altre condizioni, dopo l'esecuzione della prima queste vengono trascurate. Se nessuna condizione è vera ed è presente un else verrà eseguito il codice corrispondente; se non è presente non verrà eseguito niente.

Esercizio: scrivi due funzioni basate sugli esempi proposti, una che confronta x e y (Confronta(x, y)) e l'altra che controlla se un valore passato come parametro appartiene ad una lista di valori validi (ElaboraScelta(scelta)).

4.7 Condizioni annidate

Un'espressione condizionale può anche essere inserita nel corpo di un'altra espressione condizionale: un'espressione di questo tipo viene detta "condizione annidata".

if x == y:
  print x, "e", y, "sono uguali"
else:
  if x < y:
    print x, "e' minore di", y
  else:
    print x, "e' maggiore di", y

La prima condizione (if x == y) contiene due rami: il primo è scelto quando x e y sono uguali, il secondo quando sono diversi. All'interno del secondo (subito sotto il primo else:) troviamo un'altra istruzione if, che a sua volta prevede un'ulteriore ramificazione. Entrambi i rami del secondo if sono istruzioni di stampa ma potrebbero contenere a loro volta ulteriori istruzioni condizionali.

Sebbene l'indentazione delle istruzioni renda evidente la struttura dell'esempio, le istruzioni condizionali annidate in livelli sempre più profondi diventano sempre più difficili da leggere, quindi è una buona idea evitarle quando è possibile.

Gli operatori logici permettono un modo molto semplice di semplificare le espressioni condizionali annidate:

if 0 < x:
  if x < 10:
    print "x e' un numero positivo."

L'istruzione di stampa print è eseguita solo se entrambe le condizioni (x>0 e x<10) sono verificate contemporaneamente. Possiamo quindi usare l'operatore booleano and per combinarle:

if 0<x and x<10:
  print "x e' un numero positivo."

Questo tipo di condizione è così frequente che Python permette di usare una forma semplificata che ricorda da vicino quella corrispondente usata in matematica:

if 0 < x < 10:
  print "x e' un numero positivo."

A tutti gli effetti i tre esempi sono equivalenti per quanto riguarda la semantica (il significato) del programma.

4.8 L'istruzione return

L'istruzione return ti permette di terminare l'esecuzione di una funzione prima di raggiungerne la fine. Questo può servire quando viene riconosciuta una condizione d'errore:

import math

def StampaLogaritmo(x):
  if x <= 0:
    print "Inserire solo numeri positivi!"
    return

  risultato = math.log(x)
  print "Il logaritmo di",x,"e'", risultato

La funzione StampaLogaritmo accetta un parametro chiamato x. La prima operazione controlla che esso sia positivo; in caso contrario stampa un messaggio d'errore e termina prematuramente la funzione con return.

Ricorda che dovendo usare una funzione del modulo math è necessario importare il modulo.

4.9 Ricorsione

Abbiamo detto che è perfettamente lecito che una funzione ne chiami un'altra e di questo hai avuto modo di vedere parecchi esempi. Abbiamo invece trascurato di dirti che è anche lecito che una funzione possa chiamare sé stessa. Può non essere immediatamente ovvio il motivo per cui questo sia utile, ma questa è una delle cose più interessanti che un programma possa fare. Per fare un esempio dai un'occhiata a questa funzione:

def ContoAllaRovescia(n):
  if n == 0:
    print "Partenza!"
  else:
    print n
    ContoAllaRovescia(n-1)

ContoAllaRovescia si aspetta che il parametro sia un intero positivo. Se n vale 0, viene stampata la scritta Partenza!. Altrimenti stampa n e poi chiama la funzione ContoAllaRovescia (cioè sé stessa) con un argomento che vale n-1.

Cosa succede quando chiamiamo una funzione come questa?

>>> ContoAllaRovescia(3)

L'esecuzione di ContoAllaRovescia inizia con n=3. Dato che n non è 0, essa stampa il valore 3, e poi richiama sé stessa...

L'esecuzione di ContoAllaRovescia inizia con n=2. Dato che n non è 0, essa stampa il valore 2, poi richiama sé stessa...
L'esecuzione di ContoAllaRovescia inizia con n=1. Dato che n non è 0, essa stampa il valore 1, poi richiama sé stessa...
L'esecuzione di ContoAllaRovescia inizia con il valore di n=0. Dal momento che n è 0, essa stampa il testo "Partenza!" e poi ritorna.

La funzione ContoAllaRovescia che aveva n=1; e poi ritorna.

La funzione ContoAllaRovescia che aveva n=2; e poi ritorna.

E quindi torna in __main__ (questo è un trucco). Il risultato è questo:

3
2
1
Partenza!

Come secondo esempio torniamo alle funzioni UnaRigaVuota e TreRigheVuote:

def UnaRigaVuota():
  print

def
TreRigheVuote():
  UnaRigaVuota()
  UnaRigaVuota()
  UnaRigaVuota()

Sebbene funzionino correttamente non sarebbero di molto aiuto nel momento in cui vogliamo stampare due righe vuote o magari 106. Una alternativa migliore potrebbe essere questa:

def NRigheVuote(n):
  if n > 0:
    print
    NRigheVuote(n-1)

Questo programma è simile a ContoAllaRovescia: finché n è maggiore di 0, la funzione stampa una riga vuota e poi chiama sé stessa con un argomento n diminuito di 1.

Il processo di una funzione che richiama sé stessa è detto ricorsione, e la funzione è definita ricorsiva.

4.10 Diagrammi di stack per funzioni ricorsive

Nella sezione 3.11, abbiamo usato un diagramma di stack per rappresentare lo stato di un programma durante una chiamata di funzione. Lo stesso tipo di diagramma può aiutare a capire come lavora una funzione ricorsiva.

Ogni volta che una funzione viene chiamata, Python crea un nuovo frame della funzione, contenente le variabili locali definite all'interno della funzione ed i suoi parametri. Nel caso di una funzione ricorsiva possono esserci più frame riguardanti una stessa funzione allo stesso tempo.

La figura mostra il diagramma dello stack della funzione ContoAllaRovescia chiamata con n=3:

Come al solito il livello superiore dello stack è il frame per __main__. Questo frame è vuoto perché in questo caso non abbiamo creato alcuna variabile locale e non abbiamo passato alcun parametro.

I quattro frame di ContoAllaRovescia hanno valori diversi per il parametro n. Il livello inferiore dello stack, quando n=0, è chiamato lo stato di base. Esso non effettua ulteriori chiamate ricorsive, così non ci sono ulteriori frame.

Esercizio: disegna il diagramma dello stack per la funzione
NRigheVuote chiamata con n=4.

4.11 Ricorsione infinita

Se una ricorsione non raggiunge mai il suo stato di 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 non è generalmente considerata una buona cosa. Questo è un programma minimo che genera una ricorsione infinita:

def Ricorsione():
  Ricorsione()

Nella maggior parte degli ambienti un programma con una ricorsione infinita non viene eseguito senza fine, dato che ogni chiamata ad una funzione impegna un po' di memoria del computer e questa memoria prima o poi finisce. Python stampa un messaggio d'errore quando è stato raggiunto il massimo livello di ricorsione possibile:

  File "<stdin>", line 2, in Ricorsione
  ...
  File "<stdin>", line 2, in Ricorsione
RuntimeError: Maximum recursion depth exceeded

Questa traccia è un po' più lunga di quella che abbiamo visto nel capitolo precedente. Quando è capitato l'errore c'erano moltissime ricorsioni nello stack.

Esercizio: scrivi una funzione con ricorsione infinita ed eseguila nell'interprete Python.

4.12 Inserimento da tastiera

I programmi che abbiamo scritto finora sono piuttosto banali, nel senso che non accettano inserimenti di dati da parte dell'operatore, limitandosi a eseguire sempre le stesse operazioni.

Python fornisce un insieme di funzioni predefinite che permettono di inserire dati da tastiera. La più semplice di esse è raw_input. Quando questa funzione è chiamata il programma si ferma ed attende che l'operatore inserisca qualcosa, confermando poi l'inserimento con Invio (o Enter). A quel punto il programma riprende e raw_input ritorna ciò che l'operatore ha inserito sotto forma di stringa:

>>> Inserimento = raw_input ()
Testo inserito
>>> print Inserimento
Testo inserito

Prima di chiamare raw_input è una buona idea stampare un messaggio che avvisa l'operatore di ciò che deve essere inserito. Questo messaggio è chiamato prompt. L'operazione è così comune che il messaggio di prompt può essere passato come argomento a raw_input:

>>> Nome = raw_input ("Qual e' il tuo nome? ")
Qual e' il tuo nome? Arturo
>>> print Nome
Arturo

Se il valore da inserire è un intero possiamo usare la funzione input:

Prompt = "A che velocita'viaggia il treno?\n"
Velocita = input(Prompt)

Se l'operatore inserisce una serie di cifre questa è convertita in un intero ed assegnata a Velocita. Sfortunatamente se i caratteri inseriti dall'operatore non rappresentano un numero, il programma stampa un messaggio d'errore e si blocca:

>>> Velocita = input (Prompt)
A che velocita'viaggia il treno?
ottanta all'
ora
SyntaxError: invalid syntax

Per evitare questo tipo di errori è generalmente meglio usare la funzione \linebreak raw_input per ottenere una stringa di caratteri e poi usare le funzioni di conversione per ottenere gli altri tipi.

4.13 Glossario

Operatore modulo
operatore matematico denotato con il segno di percentuale (%) che restituisce il resto della divisione tra due operandi interi.
Espressione booleana
espressione che è o vera o falsa.
Operatore di confronto
uno degli operatori che confrontano due valori: ==, !=, >, <, >= e <=.
Operatore logico
uno degli operatori che combina le espressioni booleane: and, or e not.
Istruzione condizionale
istruzione che controlla il flusso di esecuzione del programma a seconda del verificarsi di certe condizioni.
Condizione
espressione booleana in una istruzione condizionale che determina quale ramificazione debba essere seguita dal flusso di esecuzione.
Istruzione composta
istruzione che consiste di un'intestazione terminante con i due punti (:) e di un corpo composto di una o più istruzioni indentate rispetto all'intestazione.
Blocco
gruppo di istruzioni consecutive con la stessa indentazione.
Corpo
blocco che segue l'intestazione in un'istruzione composta.
Annidamento
particolare struttura di programma interna ad un'altra, come nel caso di una istruzione condizionale inserita all'interno di un'altra istruzione condizionale.
Ricorsione
richiamo di una funzione che è già in esecuzione.
Stato di base
ramificazione di un'istruzione condizionale posta in una funzione ricorsiva e che non esegue alcuna chiamata ricorsiva.
Ricorsione infinita
funzione che chiama sé stessa ricorsivamente senza mai raggiungere lo stato di base. L'occupazione progressiva della memoria che avviene ad ogni successiva chiamata causa ad un certo punto un errore in esecuzione.
Prompt
suggerimento visivo che specifica il tipo di dati atteso come inserimento da tastiera.


Next Up Previous Hi Index