Next Up Previous Hi Index

Chapter 3

Funzioni

3.1 Chiamate di funzioni

Hai già visto un esempio di chiamata di funzione:

>>> type("32")
<type 'string'>

Il nome della funzione è type e mostra il tipo di valore della variabile. Il valore della variabile, che è chiamato argomento della funzione, deve essere racchiuso tra parentesi. È comune dire che una funzione "prende" o "accetta" un argomento e "ritorna" o "restituisce" un risultato. Il risultato è detto valore di ritorno.

Invece di stampare il valore di ritorno possiamo assegnarlo ad una variabile:

>>> betty = type("32")
>>> print betty
<type 'string'>

Come esempio ulteriore, la funzione id prende un valore o una variabile e ritorna un intero che agisce come un identificatore unico del valore:

>>> id(3)
134882108
>>> betty = 3
>>> id(betty)
134882108

Ogni valore ha un id unico che rappresenta dove è depositato nella memoria del computer. L'id di una variabile è l'id del valore della variabile cui essa si riferisce.

3.2 Conversione di tipo

Python fornisce una raccolta di funzioni interne che converte valori da un tipo all'altro. La funzione int prende ogni valore e lo converte, se possibile, in intero. Se la conversione non è possibile mostra un messaggio d'errore:

>>> int("32")
32
>>> int("Hello")
ValueError: invalid literal for int(): Hello

int può anche convertire valori in virgola mobile in interi, ma ricorda che nel farlo tronca (cioè toglie) la parte decimale.

>>> int(3.99999)
3
>>> int(-2.3)
-2

La funzione float converte interi e stringhe in numeri in virgola mobile:

>>> float(32)
32.0
>>> float("3.14159")
3.14159

Infine str converte al tipo stringa:

>>> str(32)
'32'
>>> str(3.14149)
'3.14149'

Può sembrare strano il fatto che Python distingua il valore intero 1 dal corrispondente valore in virgola mobile 1.0. Questi rappresentano effettivamente uno stesso numero ma appartengono a tipi differenti (rispettivamente intero e in virgola mobile) e quindi vengono rappresentati in modo diverso all'interno della memoria del computer.

3.3 Forzatura di tipo

Per tornare ad un esempio del capitolo precedente (la divisione di minuti per 60), ora che sappiamo convertire i tipi abbiamo un modo ulteriore per gestire la divisione tra interi. Supponiamo di dover calcolare la frazione di ora che è trascorsa: l'espressione più ovvia, minuti/60, lavora con numeri interi, così il risultato è sempre 0 anche se sono trascorsi 59 minuti.

Una delle soluzioni è quella di convertire minuti in virgola mobile e calcolare il risultato della divisione in virgola mobile:

>>> minuti = 59
>>> float(minuti) / 60.0
0.983333333333

In alternativa possiamo avvantaggiarci delle regole di conversione automatica dei tipi chiamate forzature di tipo. Nel caso di operatori matematici se uno degli operandi è float, l'altro è automaticamente convertito a float:

>>> minuti = 59
>>> minuti / 60.0
0.983333333333

Convertendo il denominatore a valore in virgola mobile forziamo Python a calcolare il risultato di una divisione in virgola mobile.

3.4 Funzioni matematiche

In matematica hai probabilmente visto funzioni del tipo sin e log, ed hai imparato a calcolare espressioni quali sin(pi/2) e log(1/x). Innanzitutto devi calcolare il valore dell'espressione tra parentesi (l'argomento). Nell'esempio pi/2 è approssimativamente 1.571 e se x vale 10.0, 1/x è 0.1.

Poi valuti la funzione stessa tramite calcoli o tabelle. sin di 1.571 è circa 1, e log in base 10 di 0.1 è -1.

Questo processo può essere applicato ripetutamente per valutare espressioni complesse del tipo log(1/sin(pi/2)). In questo caso devi iniziare dall'espressione più interna pi/2, calcolando poi il seno con sin, seguito dall'inverso del seno 1/x e dal logaritmo dell'inverso log(x).

Python è provvisto di un modulo matematico che permette di eseguire le più comuni operazioni matematiche. Un modulo è un file che contiene una raccolta di funzioni raggruppate.

Prima di poter usare le funzioni di un modulo dobbiamo dire all'interprete di caricare il modulo in memoria. Questa operazione viene detta "importazione":

>>> import math

Per chiamare una funzione di un modulo dobbiamo specificare il nome del modulo che la contiene e il nome della funzione separati da un punto. Questo formato è chiamato notazione punto.

>>> decibel = math.log10 (17.0)
>>> angolo = 1.5
>>> altezza = math.sin(angolo)

La prima istruzione assegna a decibel il logaritmo di 17 in base 10. È anche disponibile la funzione log che calcola il logaritmo naturale di un numero.

La terza istruzione trova il seno del valore della variabile angolo. sin e le altre funzioni trigonometriche (cos, tan, etc.) accettano argomenti in radianti e non i gradi. Per convertire da gradi in radianti devi dividere per 360 e moltiplicare per 2 pi. Per esempio, per calcolare il seno di 45 gradi, prima trasforma l'angolo in radianti e poi usa la funzione seno:

>>> gradi = 45
>>> angolo = gradi * 2 * math.pi / 360.0
>>> math.sin(angolo)

La costante pi fa già parte del modulo matematico math. Se conosci un po' di geometria puoi verificare il risultato confrontandolo con

2
/2
:

>>> math.sqrt(2) / 2.0
0.707106781187

3.5 Composizione

Così come in matematica anche in Python le funzioni possono essere composte, facendo in modo che il risultato di una possa essere usato come argomento di un'altra:

>>> x = math.cos(angolo + math.pi/2)

Questa istruzione prende il valore di pi (math.pi), lo divide per 2 e somma il quoziente ad angolo. La somma è poi passata come argomento alla funzione cos che ne calcola il coseno.

>>> x = math.exp(math.log(10.0))

In quest'altro esempio l'istruzione log calcola il logaritmo naturale (in base e) di 10 e poi eleva e al valore precedentemente calcolato. Il risultato viene assegnato ad x.

3.6 Aggiungere nuove funzioni

Finora abbiamo soltanto usato funzioni che fanno parte di Python, ma è possibile aggiungerne di nuove. La creazione di nuove funzioni per risolvere un particolare problema è infatti una tra le cose più utili di un linguaggio di programmazione generale, intendendo con "generale" che il linguaggio non è destinato ad un settore di applicazioni particolari, quale può essere quello scientifico o finanziario, ma che può essere usato in ogni campo).

Nel contesto della programmazione una funzione è una sequenza di istruzioni che esegue una determinata operazione. Questa azione è descritta in una definizione di funzione. Le funzioni che abbiamo usato finora sono state definite per noi e le loro definizioni sono rimaste nascoste: questa è una cosa positiva in quanto possiamo usarle senza doverci preoccupare di come sono state definite da chi le ha scritte.

La sintassi per la definizione di una funzione è:

def NOME( LISTA_DEI_PARAMETRI ):
  ISTRUZIONI

Puoi usare qualsiasi nome per una funzione, fatta eccezione per le parole riservate di Python. La lista dei parametri di una funzione specifica quali informazioni, sempre che ne sia prevista qualcuna, desideri fornire alla funzione per poterla usare.

All'interno della funzione sono naturalmente presenti delle istruzioni e queste devono essere indentate rispetto al margine sinistro. Di solito il rientro è di un paio di spazi, ma questa è solo una convenzione: per questioni puramente estetiche potresti volerne usare di più. Mentre nella maggior parte dei linguaggi il rientro è facoltativo e dipende da come il programmatore vuole organizzare visivamente il suo codice, in Python il rientro è obbligatorio. Questa scelta può sembrare un vincolo forzoso, ma ha il vantaggio di garantire una certa uniformità di stile e per quanto disordinato possa essere un programmatore il codice conserverà sempre un minimo di ordine.

La prima coppia di funzioni che stiamo per scrivere non ha parametri e la sintassi è:

def UnaRigaVuota():
  print

Questa funzione si chiama UnaRigaVuota. Le parentesi vuote stanno ad indicare che non ci sono parametri. La funzione è composta da una singola riga che stampa una riga vuota (questo è ciò che succede quando usi il comando print senza argomenti).

La sintassi per richiamare la funzione che hai appena definito è la stessa che hai usato per richiamare le funzioni predefinite:

print "Prima riga."
UnaRigaVuota()
print "Seconda riga."

Il risultato del programma è una scrittura a video:

Prima riga.

Seconda riga.

Nota lo spazio tra le due righe. Cosa avresti dovuto fare se c'era bisogno di più spazio? Ci sono varie possibilità. Avresti potuto chiamare più volte la funzione:

print "Prima riga."
UnaRigaVuota()
UnaRigaVuota ()
UnaRigaVuota ()
print "Seconda riga."

o avresti potuto creare una nuova funzione chiamata TreRigheVuote che stampa tre righe vuote:

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

print "Prima riga."
TreRigheVuote()
print "Seconda riga."

Questa funzione contiene tre istruzioni, tutte indentate di due spazi proprio per indicare che fanno parte della definizione della funzione. Dato che dopo la definizione, alla fine del terzo UnaRigaVuota(), la riga successiva,
print "Prima riga." non ha più indentazione, ciò significa che questa non fa più parte della definizione e che la definizione deve essere considerata conclusa.

Puoi notare alcune cose riguardo questo programma:

  1. Puoi chiamare più volte la stessa procedura. È abbastanza comune e utile farlo.
  2. Una funzione può chiamare altre funzioni al suo interno: in questo caso TreRigheVuote chiama UnaRigaVuota.

Può non essere ancora chiaro perché sia il caso di creare tutte queste nuove funzioni. Effettivamente di ragioni ce ne sono tante, qui ne indichiamo due:

Esercizio: scrivi una funzione chiamata NoveRigheVuote che usa TreRigheVuote per scrivere 9 righe bianche. Cosa faresti poi per scrivere 27 righe bianche?

3.7 Definizioni e uso

Raggruppando assieme i frammenti di codice della sezione precedente il programma diventa:

def UnaRigaVuota():
  print

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

print "Prima riga."
TreRigheVuote()
print "Seconda riga."

Questo programma contiene la definizione di due funzioni: UnaRigaVuota e TreRigheVuote. Le definizioni di funzione sono eseguite come le altre istruzioni ma il loro effetto è quello di creare una nuova funzione. Le istruzioni all'interno di una definizione non sono eseguite finché la funzione non è chiamata e la definizione in sé non genera alcun risultato. Come puoi facilmente immaginare, prima di poter usare una funzione devi averla definita: la definizione della funzione deve sempre precedere la sua chiamata.

Esercizio: sposta le ultime tre righe del programma all'inizio, per fare in modo che la chiamata alle funzioni appaia prima della loro definizione. Esegui il programma e vedi che tipo di messaggio d'errore ottieni.
Esercizio: inizia con il programma funzionante e sposta la definizione di UnaRigaVuota dopo la definizione di TreRigheVuote. Cosa succede quando esegui il programma?

3.8 Flusso di esecuzione

Per assicurarti che una funzione sia definita prima del suo uso devi conoscere l'ordine in cui le istruzioni sono eseguite cioè il flusso di esecuzione del programma.

L'esecuzione inizia sempre alla prima riga del programma e le istruzioni sono eseguite una alla volta dall'alto verso il basso.

La definizione di funzioni non altera il flusso di esecuzione del programma ma ricorda che le istruzioni all'interno delle funzioni non sono eseguite finché la funzione non viene chiamata. Sebbene questo non sia una cosa che avviene frequentemente, puoi anche definire una funzione all'interno di un'altra funzione. In questo caso la funzione più interna non sarà eseguita finché non viene chiamata anche quella più esterna.

La chiamata alle funzioni è una deviazione nel flusso di esecuzione: invece di proseguire con l'istruzione successiva, il flusso salta alla prima riga della funzione chiamata ed esegue tutte le sue istruzioni; alla fine della funzione il flusso riprende dal punto dov'era stato deviato dalla chiamata di funzione.

Questo è abbastanza comprensibile ma non devi dimenticare che una funzione ne può chiamare un'altra al suo interno. Può succedere che il programma principale chiami una funzione che a sua volta ne chiama un'altra: alla fine della seconda funzione il flusso torna alla prima, dov'era stato lasciato in sospeso, e quando anche la prima funzione è stata completata il flusso di esecuzione torna al programma principale.

Fortunatamente Python è sufficientemente intelligente da ricordare dove il flusso di esecuzione viene via via interrotto e sa dove riprendere quando una funzione è conclusa. Se il flusso di programma giunge all'ultima istruzione, dopo la sua esecuzione il programma è terminato.

Qual è il senso di tutto questo discorso? Quando leggi un programma non limitarti a farlo dall'alto in basso, come stessi leggendo un libro: cerca invece di seguire il flusso di esecuzione, con i suoi salti all'interno delle procedure.

3.9 Parametri e argomenti

Alcune delle funzioni che devi usare richiedono argomenti, i valori che controllano come la funzione deve portare a termine il proprio compito. Per esempio, se vuoi trovare il seno di un numero devi indicare quale sia questo numero: sin si aspetta infatti un valore numerico come argomento.

Alcune funzioni prendono due o più parametri: pow si aspetta due argomenti che sono la base e l'esponente in un'operazione di elevamento a potenza. Dentro la funzione i valori che sono passati vengono assegnati a variabili chiamate parametri.

Eccoti un esempio di definizione di una funzione con un parametro:

def Stampa2Volte(Valore):
  print Valore, Valore

Questa funzione si aspetta un unico argomento e lo assegna ad un parametro chiamato Valore. Il valore del parametro (a questo punto del programma non sappiamo nemmeno di che tipo sarà, se stringa, intero o di altro tipo) è stampato due volte. La stampa è poi conclusa con un ritorno a capo. Il nome Valore è stato scelto per ricordarti che sta a te sceglierne uno sufficientemente esplicativo, e di solito ne sceglierai qualcuno che ricordi l'uso della funzione o della variabile.

La funzione Stampa2Volte funziona per ogni tipo di dato che può essere stampato:

>>> Stampa2Volte('Pippo')
Pippo Pippo
>>> Stampa2Volte(5)
5 5
>>> Stampa2Volte(3.14159)
3.14159 3.14159

Nella prima chiamata di funzione l'argomento è una stringa, nella seconda un intero e nella terza un numero in virgola mobile (float).

Le stesse regole per la composizione che sono state descritte per le funzioni predefinite valgono anche per le funzioni definite da te, così che possiamo usare una qualsiasi espressione valida come argomento per Stampa2Volte:

>>> Stampa2Volte("Pippo"*4)
PippoPippoPippoPippo PippoPippoPippoPippo
>>> Stampa2Volte(math.cos(math.pi))
-1.0 -1.0

Come al solito, l'espressione passata come argomento è valutata prima dell'esecuzione della funzione, così nell'esempio appena proposto Stampa2Volte ritorna il risultato PippoPippoPippoPippo PippoPippoPippoPippo invece di "Pippo"*4 "Pippo"*4.

Una nota per quanto riguarda le stringhe: le stringhe possono essere racchiuse sia da virgolette "ABC" che da apici 'ABC'. Il tipo di delimitatore NON usato per delimitare la stringa, l'apice se si usano le virgolette, le virgolette se si usa l'apice, può essere usato all'interno della stringa. Ad esempio sono valide le stringhe "apice ' nella stringa" e 'virgoletta " nella stringa', ma non lo sono 'apice ' nella stringa' e "virgoletta " nella stringa", dato che in questo caso l'interprete non riesce a stabilire quale sia il fine stringa desiderato dal programmatore.

Esercizio: scrivi una chiamata a Stampa2Volte che stampa a video la stringa "Pippo"*4 "Pippo"*4 così com'è scritta.

Naturalmente possiamo usare una variabile come argomento di una funzione:

>>> Messaggio = 'Come va?'
>>> Stampa2Volte(Messaggio)
Come va? Come va?

Il nome della variabile che passiamo come argomento (Messaggio) non ha niente a che fare con il nome del parametro nella definizione della funzione (Valore). Non ha importanza conoscere il nome originale con cui sono stati identificati i parametri durante la definizione della funzione.

3.10 Variabili e parametri sono locali

Quando crei una variabile locale all'interno di una funzione, essa esiste solo all'interno della funzione e non puoi usarla all'esterno. Per esempio:

def StampaUnite2Volte(Parte1, Parte2):
  Unione = Parte1 + Parte2
  Stampa2Volte(Unione)

Questa funzione prende due argomenti, li concatena e poi ne stampa il risultato due volte. Possiamo chiamare la funzione con due stringhe:

>>> Strofa1 = "Nel mezzo "
>>> Strofa2 = "del cammin"
>>> StampaUnite2Volte(Strofa1, Strofa2)
Nel mezzo del cammin Nel mezzo del cammin

Quando StampaUnite2Volte termina, la variabile Unione è distrutta. Se proviamo a stamparla quando il flusso di esecuzione si trova all'esterno della funzione StampaUnite2Volte otterremo un messaggio d'errore:

>>> print Unione
NameError: Unione

Anche i parametri sono locali: al di fuori della funzione StampaUnite2Volte, non esiste alcuna cosa chiamata messaggio. Se proverai ad usarla al di fuori della funzione dov'è definita Python ti mostrerà ancora una volta un messaggio d'errore.

3.11 Diagrammi di stack

Per tenere traccia di quali variabili possono essere usate è talvolta utile disegnare un diagramma di stack. Come i diagrammi di stato, i diagrammi di stack mostrano il valore di ciascuna variabile e indicano a quale funzione essa appartenga.

Ogni funzione è rappresentata da un frame, un rettangolo con il nome della funzione a fianco e la lista dei parametri e delle variabili al suo interno. Il diagramma di stack nel caso dell'esempio precedente è:

L'ordine dello stack mostra chiaramente il flusso di esecuzione. Possiamo vedere che Stampa2Volte è chiamata da StampaUnite2Volte e che StampaUnite2Volte è chiamata da __main__. __main__ è un nome speciale che indica il programma principale che di per sé (non essendo definito con def come si fa per le funzioni) non ha un nome vero e proprio. Quando crei una variabile all'esterno di ogni funzione, essa appartiene a __main__.

Ogni parametro si riferisce al valore che ha l'argomento corrispondente. Così Parte1 ha lo stesso valore di Strofa1, Parte2 ha lo stesso valore di Strofa2 e Valore lo stesso di Unione.

Se avviene un errore durante la chiamata di una funzione, Python mostra il nome della funzione, il nome della funzione che l'ha chiamata, il nome della funzione che ha chiamato quest'ultima e così via, fino a raggiungere il primo livello che è sempre __main__.

Ad esempio se cerchiamo di chiamare Unione dall'interno di Stampa2Volte, otteniamo un errore di tipo NameError:

Traceback (innermost last):
  File "test.py", line 13, in __main__
    StampaUnite2Volte(Parte1, Parte2)
  File "test.py", line 5, in StampaUnite2Volte
    Stampa2Volte(Unione)
  File "test.py", line 9, in Stampa2Volte
    print Unione
NameError: Unione

Questa lista temporale delle chiamate delle funzioni è detta traccia. La traccia ti dice in quale file è avvenuto l'errore, che riga all'interno del file si stava eseguendo in quel momento ed il riferimento alla funzione che ha causato l'errore.

Nota che c'è una notevole somiglianza tra traccia e diagramma di stack e questa somiglianza non è certamente una coincidenza.

3.12 Funzioni con risultati

Puoi notare come alcune delle funzioni che hai usato, tipo le funzioni matematiche, restituiscono dei risultati. Altre funzioni, come UnaRigaVuota, eseguono un'azione senza ritornare alcun valore. Questa differenza solleva qualche domanda:

  1. Cosa succede se chiami una funzione e non fai niente con il risultato che viene restituito (per esempio non lo assegni ad una variabile e non lo usi come parte di una espressione)?
  2. Cosa succede se usi una funzione che non produce risultato come parte di un'espressione (per esempio UnaRigaVuota() + 7)?
  3. Puoi scrivere funzioni che producono risultati, o sei costretto a limitarti a semplici funzioni tipo UnaRigaVuota e Stampa2Volte che eseguono azioni in questo caso piuttosto banali?

La risposta alla terza domanda la troveremo al capitolo 5.

Esercizio: trova la risposta alle altre due domande provando i due casi. Quando non hai chiaro cosa sia legale e cosa non lo sia è buona regola provare per vedere come reagisce l'interprete.

3.13 Glossario

Chiamata di funzione
istruzione che esegue una funzione. Consiste di un nome di funzione seguito da una serie di argomenti racchiuso tra parentesi.
Argomento
valore fornito alla funzione quando questa viene chiamata. Il valore è assegnato al corrispondente parametro della funzione.
Valore di ritorno
risultato di una funzione.
Conversione di tipo
istruzione esplicita che prende un valore di un tipo e lo converte nel corrispondente valore di un altro tipo.
Forzatura di tipo
conversione automatica di tipo secondo le regole di forzatura di Python.
Modulo
file che contiene una raccolta di funzioni correlate.
Notazione punto
sintassi per la chiamata di una funzione definita in un altro modulo, specificando il nome del modulo di appartenenza, seguito da un punto e dal nome della funzione con gli eventuali argomenti tra parentesi.
Funzione
sequenza di istruzioni identificata da un nome che svolge qualche operazione utile. Le funzioni possono avere o meno dei parametri e possono produrre o meno un risultato.
Definizione della funzione
istruzioni che creano una nuova funzione, specificandone il nome, i parametri e le operazioni che essa deve eseguire.
Flusso di esecuzione
ordine in cui le istruzioni sono interpretate quando il programma viene eseguito.
Parametro
nome usato all'interno della funzione per riferirsi al valore passato come argomento.
Variabile locale
variabile definita all'interno di una funzione. Una variabile locale può essere usata unicamente all'interno della funzione dov'è definita.
Diagramma di stack
rappresentazione grafica delle funzioni, delle loro variabili e dei valori cui esse si riferiscono.
Frame
rettangolo che in un diagramma di stack rappresenta una chiamata di funzione. Indica le variabili locali e i parametri della funzione.
Traccia
lista delle funzioni in corso di esecuzione stampata in caso di errore in esecuzione.


Next Up Previous Hi Index