2: Primi passi

  • Come eseguire le operazioni matematiche in Python
  • Come stampare un risultato
  • Come definire e utilizzare le variabili
  • Come definire una funzione

2.1 Jupyter Lab/Notebook¶

Per eseguire comandi di Python, noi useremo una interfaccia potente, il Jupyter Notebook. Il notebook è diviso in celle. I due tipi di celle che ci interessano sono il tipo Code dove si possono scrivere ed eseguire comandi, e il tipo Markdown che contengono testo. Mescolare celle eseguibili e celle di testo in cui spiegare il procedimento che si sta utilizzando oppure dare informazioni per rendere comprensibile il codice permette di creare dei documenti interattivi.

Lo stile delle celle di testo può essere specificato usando comandi Markdown e/o Latex. Blocchi di codice hanno di fronte il prompt In[#] oppure [#], dove # è un intero progressivo che identifica la cella. Per eseguire la cella, schiacciate Shift-Enter (Maiusc-Invio).

In [ ]:
4 + 5

In una cella ci possono essere più comandi, ognuno sulla propria linea. Il risultato dell'ultimo comando viene scritto sullo schermo (a meno che venga assegnato a una variabile. Ne parleremo più tardi).

In [ ]:
4 + 5
7 + 33

Per stampare i risultati delle operazioni precedenti l'ultima possiamo usare print:

In [ ]:
print(4 + 5)
7 + 33

2.2 Operazioni elementari¶

Le operazioni elementari come l'addizione (+), sottrazione (-), moltiplicazione (*), divisione (/) e esponenziazione (**) funzionano come ci si aspetta.

Attenzione!

In un numero reale, il carattere che separa la parte intera da quella decimale è il punto `.`, non la virgola `,`!
In [ ]:
10 + 10000
In [ ]:
42 - 1.5
In [ ]:
47 * 11
In [ ]:
10 / 0.5
In [ ]:
2**2   
In [ ]:
2**4

Usando il fatto che $\sqrt[n]{x} = x^{1/n}$, possiamo calcolare $\sqrt{3} = 1.732050\dots$ con il simbolo **:

In [ ]:
3**0.5

Imparare Facendo

  1. Calcolate 100*4 e 100.*4 . In cosa differiscono i risultati?
  2. Calcolate 100/4 e 100./4 . I risultati differiscono?
  3. Calcolate il prodotto dei primi dieci numeri interi positivi (1,....,10).

All'interno delle celle di tipo Code è possibile inserire commenti (che vengono ignorati quando la cella viene eseguita).

In [ ]:
# Questo è un commento
2 + 2
In [ ]:
2 + 2  # un commento sulla stessa di un comando

2.3 Regole di precedenza¶

a < b significa che a viene eseguita prima di b:

                Funzioni < parentesi < esponenziazione < moltiplicazione, divisione < addizione, sottrazione


In [ ]:
2**4/3     # Interpretato come (2**4)/3, non come 2**(4/3)

Le parentesi tonde (e solo quelle tonde) servono per raggruppare:

In [ ]:
2 * 10 + 5
In [ ]:
2 * (10 + 5)
In [ ]:
2**(4/3)

Imparare Facendo

  1. Calcolate la terza potenza della radice quadrata di 317.457 .
  2. Calcolate la radice cubica del quadrato del risultato precedente. Il risultato è quello atteso?

2.4 Funzioni intrinseche¶

Sono funzioni sempre disponibili in Python. Per esempio, le funzioni che trasformano una variabile da un tipo ad un altro (Si veda 03_Tipi_di_Dati.ipynb) oppure max, min,abs. La lista completa delle funzioni intrinseche si trova in Built-in functions.
Qualche esempio:

Se a e b sono interi, a % b fornisce il modulo di a rispetto a b cioè il resto della divisione di a per b.

In [ ]:
8 % 5
In [ ]:
100 % 9

Se a e b sono interi, a // b fornisce la parte intera di a/b.

In [ ]:
9//2
In [ ]:
23//8

2.5 Funzioni Matematiche ¶

La quasi totalità delle funzioni in Python sono contenute in librerie dette moduli che devono essere importati per poter utilizzare le funzioni al loro interno. Vanno importate solo le librerie che vengono utilizzate. Le funzioni matematiche più comuni come seno (sin), coseno (cos), esponenziale (exp), logaritmo (log) e molte altre si trovano in un modulo chiamato math. Per utilizzare le funzioni dobbiamo importare il modulo:

In [ ]:
import math
math.exp(1.0)

Usando la funzione dir, possiamo vedere la lista dei metodi e funzioni disponibili in un modulo:

In [ ]:
dir(math)

La funzione help fornisce informazioni più dettagliate:

In [ ]:
help(math)

help(nome_modulo.nome_funzione) fornisce informazioni sulla funzione nome_funzione contenuta nel modulo nome_modulo:

In [ ]:
help(math.exp)

help(nome_variabile) mostra tutti i metodi e funzioni che si possono far agire sulla variabile:

In [ ]:
a = 37.79
help(a)

Se le informazioni fornite da dir e help non sono sufficientemente dettagliate o chiare, internet viene in soccorso. Cercate "python math" nel browser.

Il modulo math definisce le costanti π e e:

In [ ]:
math.pi
In [ ]:
math.e
In [ ]:
math.cos(math.pi)     # L'argomento delle funzioni trigonometriche è in radianti
In [ ]:
math.log(math.e)      # La funzione log ha base "e"

Imparare Facendo

  1. Importate math.
  2. Calcolate il seno di $\pi/3$. Calcolate il seno di $2 \pi/3$.
  3. Calcolate il coseno iperbolico del logaritmo naturale di 133.
  4. Calcolate la somma del coseno al quadrato di 43 e del seno al quadrato di 43.
  5. Calcolate la differenza fra il coseno iperbolico al quadrato di 77 e il seno iperbolico al quadrato di 77.
    Quanto deve valere $cosh(x)^2 - sinh(x)^2$ qualunque sia $x$?
    Calcolate la differenza fra il coseno iperbolico al quadrato di 1/2 e il seno iperbolico al quadrato di 1/2.
  6. Cercate informazioni sulla funzione degrees usando help.
  7. Cercate informazioni sulla funzione factorial usando help.
  8. Calcolate il fattoriale di 0.47 .

2.6 Variabili¶

Una variabile può essere usare per immagazzinare il valore di un oggetto. In Python, tutti i numeri (e tutto il resto, incluse funzioni moduli e files) sono oggetti. Una variabile viene creata attraverso una assegnazione. Per assegnare il valore a una variabile si usa il segno (’=’).
La struttura di una assegnazione è sempre: nome_variabile = valore.
I nomi delle variabili possono contenere solo i caratteri a-z, A-Z, 0-9 e l'underscore "". Il primo carattere del nome di una variabile non può essere un numero. `a1bè legale;x+1,1aea(cd` non lo sono.

In [ ]:
x = 0.5

Una volta che la variabile x è stata creata assegnandogli il valore 0.5, come nell'esempio, possiamo utilizzarla in ulteriori comandi:

In [ ]:
x*3
In [ ]:
x**2
In [ ]:
width = 20
height = 5 * 9
area = width * height
print("Area =",area)

Quando a una variabile viene assegnato un nuovo valore il contenuto della memoria viene sovrascritto, il valore precedente viene perso:

In [ ]:
width = 15.7
print("width =",width)

Quando area viene calcolata, solo il suo valore viene salvato in memoria. Se si vuole che area rifletta il nuovo valore di width è necessario ricalcolarla.

In [ ]:
print("Area =",area)
area = width * height
print("New Area =",area)

È possibile assegnare un valore a più variabili contemporaneamente:

In [ ]:
x = y = z = 0  # inizializza x, y e z a 0
x
In [ ]:
y
In [ ]:
z

Le variabili devono essere create (assegnando loro un valore) prima di poterle utilizzare, altrimenti si ha un errore :

In [ ]:
# Tentativo di accedere ad una variabile non definita
n

Potete trovare una buona introduzione a come interpretare i messaggi di errore di Python in https://realpython.com/python-traceback/. È utile consultarla regolarmente. Ovviamente è in inglese.

Python è "case sensitive". var, Var e vAr sono variabili diverse:

In [ ]:
Var = 1
var = 33
In [ ]:
print('Var:',Var)
print('var:',var)

Un po' di dettagli e di terminologia¶

Per essere precisi, quando scrivete

In [ ]:
x = 0.5

questo è quello che succede.
Per prima cosa, Python crea l'oggetto 0.5. Tutto in Python è un oggetto, e quindi lo è anche il numero reale (floating point) 0.5. Questo oggetto viene immagazzinato da qualche parte in memoria. Poi Python lega un nome all'oggetto (binds a name to the object). Il nome è x, e spesso ci si riferisce a x come a una variabile, un oggetto, o persino il valore 0.5. Tuttavia, tecnicamente, x è il nome che è legato all'oggetto 0.5. Un altro modo per dirlo è che x è una referenza (reference) all'oggetto.

Mentre in genere è sufficiente pensare di assegnare 0.5 alla variabile x, ci sono situazioni in cui è necessario ricordarsi di quello che effettivamente succede. In particolare, quando passiamo, come argomento a delle funzioni, delle reference a oggetti , dobbiamo essere coscienti che le funzioni possono agire sull'oggetto piuttosto che su una copia dell'oggetto. Tutto questo verrà esaminato con maggiori dettagli in 06_funzioni_moduli.

2.7 Equazioni Impossibili¶

In un programma troviamo spesso espressioni come

In [ ]:
x = x + 1

Se interpretiamo questa espressione come una equazione, come siamo abituati a fare in matematica, in $x = x + 1$ potremmo sottrarre $x$ da entrambi i membri, ottenendo 0 = 1. Sappiamo che questo non è vero quindi da qualche parte c'è qualcosa che non torna.

La risposta è che le “equazioni“ nei programmi non sono equazioni ma assegnazioni. Devono sempre essere comprese come una sequenza di due passi:

  1. Calcola il valore dell'espressione a destra dell'uguale

  2. Assegna questo valore alla variabile il cui nome appare sulla sinistra. (In Python: lega il nome sulla sinistra all'oggetto che si trova a destra.)

Qualche testo di Informatica usa la notazione seguente per esprimere l'assegnazione e evitare confusioni con le equazioni matematiche:

$$x \leftarrow x + 1$$

Applichiamo la procedura a due passi all'espressione x = x + 1 vista sopra:

  1. Calcola il valore dell'espressione a destra dell'uguale: per farlo dobbiamo conoscere il valore attuale di x. Assumiamo che x attualmente sia 4. In questo caso, il membro di destra, x+1, viene valutato come 5.

  2. Assegna questo valore (i.e. 5) alla variabile il cui nome appare sulla sinistra, x.

Controlliamo che questa interpretazione sia corretta:

In [ ]:
x = 4     
x = x + 1
x

La notazione +=¶

È molto comune dover aumentare la variabile x di una quantità costante c. Possiamo scrivere:

x += c

invece di

x = x + c

Il nostro esempio iniziale avrebbe potuto essere scritto come

In [ ]:
x = 4
x += 1
x

Lo stesso tipo di operatore è definito per la moltiplicazione per una costante (*=), suttrazione di una costante (-=) e divisione per una costante (/=).

Notate che l'ordine fra + e = è rilevante:

In [ ]:
x += 1

incrementa la variabile x di uno mentre

In [ ]:
x =+ 1

assegna il valore +1 alla variabile x.

Imparare Facendo

  1. Assegnate il valore 3.2 alla variabile x.
  2. Sommate a x, uno alla volta, 1.3, 0.5, 1.7 .
  3. Ad ogni passaggio stampate il valore di x.

2.8 Le funzioni¶

Le funzioni ci permettono di raggruppare una sequenza di comandi in un blocco logico. Comunichiamo con una funzione attraverso una interfaccia ben definita, fornendo dei parametri, e ricevendo di ritorno delle informazioni. Generalmente non sappiamo esattamente come una funzione ottiene il valore che ci restituisce, conosciamo solo l'interfaccia.

Per esempio la funzione math.sqrt: non sappiamo esattamente come calcola la radice quadrata, ma conosciamo l'interfaccia: se passiamo il valore x alla funzione, ci restituisce (un valore approssimato per) $\sqrt{x}$.

Questa astrazione è utile: è una tecnica comune dividere un sistema, un problema in componenti più piccole che funzionano insieme attraverso delle interfacce ben definite che non hanno bisogno di conoscere i dettagli di come ciascuna realizza il suo compito. In fatti, non doversi preoccupare dei dettagli di implementazione ci permette di avere una visione più chiara di un sistema composto di molte parti.

Le funzioni costituiscono i mattoncini fondamentali all'interno di programmi più vasti e aiutano a tenere sotto controllo la complessità intrinseca dei problemi.

Nella programmazione, la parola “funzione” si riferisce ad una sequenza, dotata di nome in modo da poter essere "chiamata", di operazioni che svolgono un determinato calcolo. Per esempio, la funzione sqrt() nel modulo math calcola la radice quadrata di un dato valore:

In [ ]:
import math
math.sqrt(4)

Il valore che passiamo alla funzione sqrt in questo caso è 4. Questo valore viene chiamato argomento della funzione. Una funzione può avere più argomenti.

La funzione ritorna il valore 2.0 (il risultato del calcolo) all'“ambiente di chiamata”. Questo valore si chiama il return value della funzione.

Si dice in genere che la funzione prende un argomento e ritorna un risultato o return value.

2.9 Come definire una nostra funzione¶

Il format generico della definizione di una funzione è:

def my_function(arg1, arg2, ..., argn):        # Notate i ":" finali
    """Optional docstring."""

    # Implementation of the function
    comando1                                # Notate l'indentazione di 4 spazi di tutti i comandi contenuti all'interno della funzione
    comando2

    return result1, result2 ...  # optional                 

#this is not part of the function
some_command                                  # Notate l'assenza di indentazione
  • La funzione inizia con la keyword def, seguita dal nome che assegnamo alla funzione, dalla lista, tra parentesi, degli argomenti che devono essere passati alla funzione quando questa viene eseguita e dal simbolo : che chiude la dichiarazione della funzione.

  • Tutto il corpo della funzione, l'insieme delle operazioni che devono essere eseguite sugli argomenti in input, devo essere indentati di quattro spazi rispetto alla keyword def.

  • È buona pratica inserire una docstring, fra tripli doppi apici(""") che spieghi cosa fa la funzione. La docstring può essere mostrata con help(nome_funzione).

  • In genere la funzione restituisce i risultati delle proprie operazioni al programma esterno attraverso la keyword return

  • La definizione della funzione si conclude quando il codice smette di essere indentato.

  • Una funzione, una volta definita e messa in memoria eseguendo la cella, può essere chiamata più volte, anche con argomenti diversi.

  • Stampare un valore all'interno di una funzione non significa rendere disponibile il valore all'esterno della funzione. In particolare

    return print(a)
    

        stampa il valore della variabile a sullo schermo ma restituisce None (v. Sez "Un errore comune").

Le parentesi dopo il nome della funzione sono necessarie. Se una funzione non ha argomenti si scrive:

def my_function2():

Se si omettono le parentesi si ha un errore:

In [ ]:
def pippo:
    pass

La terminologia di Allen Downey (nel suo libro Think Python) di funzioni fruttuose e infruttuose distingue fra funzioni che ritornano un valore (=fruitful) e quelle che non lo fanno (=fruitless). Se una funzione non usa il comando return, diciamo che non ritorna nulla (mentre, in realtà ritorna sempre l'oggetto None – anche se il comando return manca).

Per esempio, la funzione greeting, quando viene chiamata, stampa “Hello World” (ed è fruitless perché non ritorna alcun valore).

In [ ]:
def greeting():
    print("Hello World!")

Se la chiamiamo:

In [ ]:
greeting()

stampa “Hello World” su stdout (lo standard output, in questo caso la nostra finestra), come ci si aspetta. Se assegnamo il return value della funzione a una variabile x, la possiamo successivamente esaminare/utilizzare:

In [ ]:
x = greeting()
In [ ]:
print(x)

confermandondo che la funzione greeting ha effettivamente ritornato l'oggetto None.

In genere, funzioni che ritornano uno o più valori sono più utili perché possono essere combinate per costruire il codice (magari all'interno di un'altra funzione).

Esempio 1¶

Una funzione che prende come input due numeri e ne restituisce la somma.

Attenzione!

La cella seguente definisce la funzione. Eseguendo la cella la funzione viene caricata in memoria. Nulla viene calcolato.
In [ ]:
def my_sum(a,b):
    """ 
    my_sum calcola la somma di due valori.
    a, b   : input
    c      : output
    """
    c = a+b
    return c

Attenzione!

Nella cella seguente la funzione viene chiamata con input 4 e 5. I valori 4 e 5 vengono sostituiti alle variabili a e b, vengono eseguite le operazioni contenute nel corpo della funzione e il risultato viene restituito. Il risultato può essere assegnato a una variabile e ulteriormente utilizzato.
In [ ]:
my_sum(4,5)

Per poter utilizzare il risultato di una funzione all'esterno della funzione stessa è necessario assegnarlo ad una variabile:

In [ ]:
f1 = my_sum(3.7,-2.4)
f2 = f1*f1
print(f2)

La docstring fornisce informazioni sullo scopo della funzione. Si può richiamare con help

In [ ]:
help(my_sum)

Esempio 2¶

Una funzione senza input che restituisce il valore di $\pi/2$:

In [ ]:
def my_half_pi():    
    return 3.141592653589793/2.

my_half_pi()

Esempio 3¶

Una funzione che restituisce due valori:

In [ ]:
def my_sum_dif(a,b):
    sum = a + b
    dif = a - b
    return sum, dif

I due valori restituiti da my_sum_dif possono essere assegnati a due variabili separando i nomi delle variabili con una virgola.

In [ ]:
p = 8
q = 2

r1,r2 = my_sum_dif(p,q)

print(p,'+',q,'=',r1)
print(p,'-',q,'=',r2)

Esempio 4¶

Una funzione che restituisce le soluzioni di una equazione algebrica di secondo grado, cioè i valori di x tali che a*x**2 + b*x +c = 0. Gli input necessari sono a, b, c.

In [ ]:
import math

def my_sol_eq2(a,b,c):
    disc = math.sqrt(b*b-4.*a*c)
    sol1 = (-b+disc)/2./a
    sol2 = (-b-disc)/2./a
    return sol1,sol2

a = 1
b = 2
c =1
r1,r2 =  my_sol_eq2(a,b,c)
print('sol+ :',r1)
print('sol2 :',r2)
La funzione `my_sol_eq2` darà sempre la risposta corretta?

Possiamo definire più funzioni di Python in un singolo file e chiamare una funzione in una funzione diversa. Ecco un esempio:

In [ ]:
def returnstars( n ):
    return n * '*'

def print_centered_in_stars( string ):
    linelength = 46 
    starstring = returnstars((linelength - len(string)) // 2)

    print(starstring + string + starstring)

print_centered_in_stars('Hello world!')
Informazioni ulteriori¶
  • Python Tutorial: Section 4.6 Defining Functions
  • 06_funzioni_moduli.ipynb

2.10 Un errore comune: stampare un valore verso ritornare un valore¶

È un errore comune, quando si inizia, confondere stampare un valore con ritornare un valore. Nell'esempio seguente è difficile capire se la funzione math.sin ritorna un valore oppure lo stampa:

In [ ]:
import math
math.sin(2)

Importiamo il modulo math, e chiamiamo la funzione math.sin con argomento 2. La chiamata math.sin(2), in effetti, ritorna il valore 0.909..., non lo stampa. Tuttavia, siccome non abbiamo assegnato il valore a una variabile, la sessione interattiva di Python stampa sullo schermo il valore ritornato.

La sequenza alternativa che segue funziona solamente se viene restituito un valore:

In [ ]:
x = math.sin(2)
print(x)

Il risultato della chiamata math.sin(2) viene assegnato alla variabile x, e , alla linea successiva, x viene stampato.

In genere le funzioni vengono eseguite in modo "silenzioso" (cioè non stampano nulla) e comunicano il risultato del loro conto attraverso il return value.

Parte della confusione fra valori stampati e valori ritornati in una sessione interattiva deriva dal fatto che Python stampa il valore che viene ritornato se il valore non viene assegnato a una variabile. In genere, noi vogliamo vedere quello che viene ritornato, per poterci orientare. Bisogna però superare questa confusione iniziale.

Ulteriori informazioni¶
  • "Think Python" di Allen Downey fornisce una introduzione semplice alle funzioni in chapter 3 (Functions) and chapter 6 (Fruitful functions).
In [ ]:
def square1(a):
    sq = a*a
    return print(sq)
In [ ]:
Testiamo la funzione:
In [ ]:
square1(3)

Apparentemente square funziona, ma, in realtà, stampa solo il risultato sullo schermo, NON restituisce il valore. Per verificarlo, assegniamo quanto restituito dalla funzione ad una variabile o poi stampiamone il contenuto:

In [ ]:
d = square1(5)
print('Il valore della variabile d è:',d)

Il comando return print(sq) STAMPA sullo schermo il risultato ma RESTITUISCE None.

Il modo corretto di fare è:

In [ ]:
def square2(a):
    sq = a*a
    return sq
In [ ]:
square2(3) #
In [ ]:
d = square2(5)
print('Il valore della variabile d è:',d)

Se non c'è il return la funzione restituisce None:

In [ ]:
def square3(a):
    sq = a*a

d=square3(3)
print('La funzione square3 restituisce:',d)

Imparare Facendo

  1. Costruite una funzione ipotenusa di argomenti, c1 e c2, che date le lunghezze di due cateti restituisca la lunghezza dell'ipotenusa del triangolo rettangolo corrispondente. Testate la funzione con due cateti di lunghezza 3. e 4. .
     
  2. Costruite una funzione my_min con due argomenti, a e b, che restituisca il più piccolo dei due argomenti.
    1. Assegnate alla variabile x il valore 3/7 e alla variabile y il valore 7/16.
    2. Utilizzate my_min per determinare il minore fra x e y.
    3. Assegnate il valore restituito da my_min alla variabile z.
    4. Stampate il valore del quadrato di z.