Le funzioni ci permettono di raggruppare una sequenza di comandi in un blocco logico. La funzione è identificata da un nome in modo da poter essere "chiamata". 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}$.
Possiamo raggruppare diverse funzioni in un modulo di Python e creare le nostre librerie di utilità.
Il format generico della definizione di una funzione è:
def my_function(arg1, arg2, ..., argn):
"""Optional docstring."""
# Implementation of the function
return result # optional
#this is not part of the function
some_command
Le parentesi dopo il nome della funzione sono necessarie.
Per funzioni definite dall'utente (classi, tipi, moduli, …), dovrebbe sempre essere presente una docstring sintetica ma esauriente. Sei mesi dopo aver scritto un pezzo di codice, anche l'autore ha difficoltà a comprenderlo senza un buon apparato di commenti. "Se non l'hai documentato, non l'hai fatto": severo ma giusto.
Come documentare una funzione definita dall'utente:
def power2and3(x):
"""Returns the tuple (x**2, x**3)"""
return x**2 ,x**3
power2and3(2)
La documentazione può essere recuperata con il comando help
help(power2and3)
Possiamo definire più funzioni di Python in un singolo file e utilizzare una funzione all'interno di una funzione diversa. Ecco un esempio:
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!')
Python permette di definire valori di default per gli argomenti di una funzione. Ecco un esempio: la funzione myfunc
prende tre argomenti: x
, p
e debug
. Il primo argomento x
è una variabile “posizionale”; deve essere presente nella chiamata alla funzione. Il secondo argomento p
ha il valore di default 2, il terzo debug
ha come valore di default False. Questi argomenti sono opzionali: se l'utente chiama questa funzione con un solo argomento, l'argomento viene assegnato a x
mentre p
e debug
assumeranno i valori di default. Se vengono forniti due argomenti (senza nome) il primo viene assegnato a x
e il secondo a p
mentre debug
assumera il valore di default e così via. Quindi è possibile passare un parametro opzionale senza utilizzare la keyword, tuttavia il modo migliore e più comprensibile di usare i parametri di default è quello di utilizzare espicitamente la keyword che li individua. In questo modo non è necessario ripettare l'ordine degli argomenti.
def myfunc(x, p=2, debug=False):
if debug:
print("evaluating myfunc for x = " + str(x) + " using exponent p = " + str(p))
return x**p
myfunc(5)
myfunc(5, 3)
myfunc(5, debug=True)
Anche gli argomenti posizionali possono essere passati per nome:
myfunc(p=3, debug=True, x=7)
Se non vengono passati tutti i parametri posizionali si ha un errore:
myfunc(p=3, debug=True)
Nella definizione di una funzione gli argomenti con nome devono venire dopo tutti gli argomenti posizionali. In caso contrario si ha un errore quando la funzione viene caricata in memoria:
def pippo(message='Hello ', name):
print(message,name)
Per esempio help(print)
restituisce:
*args
e **kwargs
¶Abbiamo visto che in Python possiamo accedere agli elementi di un oggetto iterabile (lista, ntupla, stringa, ...) attraverso gli indici:
en = (3,4,5,6,7,8)
en[0]
en[1:4]
en[-1]
Possiamo anche assegnare gli elementi di un iterabile a variabili singole, operazione che si chiama spacchettamento, purchè il numero di variabili corrisponda al numero di elementi dell'iterabile:
a,b,c,d,e,f = en
print(f"c:{c}, a:{a}")
*
per sequenze¶L'operatore *
ci permette di essere flessibili, definendo un oggetto con un numero variabile di elementi.
Nell'esempio seguente en_1
e en_2
catturano i primi due elementi della lista. en_last
cattura l'ultimo elemento.
Tutti gli altri elementi, qualunque sia il loro numero, vengono catturati
da *en2_rest
. La variabile en2_rest
(senza *
) contiene la lista degli elementi dall'indice 2 a len(en)-1.
en_1, en_2, *en_rest, en_last = en
print(f"en_1:{en_1}, en_2:{en_2}, en_last:{en_last}, en_rest:{en_rest}\n")
print(f"Il tipo di en_rest è {type(en_rest)}")
Questa notazione funziona anche se non ci sono elementi da spacchettare:
a,*b = [1]
print(f"a = {a}, b = {b}")
L'operatore *
permette anche di unire facilmente sequenze:
a = [1,3,5]
b = (7.3,8.1)
c = [*a,*b]
c
L'operatore di impacchettamento/spacchettamento *
permette di costruire funzioni con un numero indefinito di argomenti usando la notazione:
def my_function(*args):
Tradizionalmente si usa il nome args
, ma come sempre i nomi degli argomenti di una funzione può essere qualunque.
# Definire una funzione con *args
def show_countries(*args):
print(f"The args type: {type(args)}")
for item in args:
print(item)
# Chiamata alla funzione con due argomenti
show_countries('America', 'Canada')
# Chiamata alla funzione con tre argomenti
show_countries('America', 'Canada', 'Mexico')
# Usando una ntupla senza unpacking. Stiamo passando un solo argomento
inp = ('America', 'Canada')
show_countries(inp)
# Usando una tuple spacchettandola con *
inp = ('America', 'Canada')
show_countries(*inp)
Una funzione che usa l'impacchettamento per sommare su un numero arbitrario di argomenti:
def mySum(*args):
return sum(args)
# Chiamata
print(mySum(1, 2, 3, 4, 5))
print(mySum(10, 20))
L'operatore *
si può utilizzare nella chiamata a una funzione con numero fisso di argomenti purchè questa operazione produca il numero corretto di argomenti:
# Una semplice funzione che prende tre argomenti e li stampa
def fun1(a, b, c):
print(a, b, c)
# arg_list viene spacchettato (in questo caso deve contenere esattamente tre elementi)
# e il suo contenuto viene passato a fun1
arg_list = ['Hello', 'beautiful', 'world!']
fun1(*arg_list)
Un'altra funzione che prende un numero arbitrario di argomenti impacchettati nella tuple *args.
def fun2(*args):
# La tuple args viene convertita in lista per poterne modificare gli elementi
args = list(args)
# Gli elementi vengono modificati. Devono essere presenti almeno due elementi
args[0] = 'Greetings,'
args[1] = 'awesome'
return args
output = fun2('Hello', 'beautiful', 'world!', 'Today','is', 'gorgeous.')
print(output)
**
, per argomenti con nome e dizionari¶L'operatore di impacchettamento/spacchettamento **
permette di costruire funzioni con un numero indefinito di argomenti con nome usando la notazione:
def my_function(**kwargs):
Il nome kwargs
è puramente tradizionale.
# Un esempio in cui un gruppo di argomenti con nome viene impacchettato in un dizionario usando **
def fun(**kwargs):
# kwargs is a dict
print(type(kwargs))
# Printing dictionary items
for key in kwargs:
val = kwargs[key]
print(f"{key} = {val}")
Chiamata a fun
con un argomento:
fun(language="Python")
Chiamata a fun
con tre argomenti:
fun(name="geeks", ID="101", language="Python")
Oppure:
my_kwargs = dict(name="geeks", ID="101", language="Python")
fun(**my_kwargs)
L'operatore **
può essere utilizzato anche per creare o unire dizionari. Nell'esempio seguente creiamo un dizionario voti0
e poi usiamo **
per creare una copia indipendente (profonda) voti1
di voti0
:
voti0 = {'Chiara':29, 'Roberto':24}
voti1 = {**voti0}
print(f" voti0 : {voti0}, voti1 : {voti1}")
print(f" id(voti0) : {id(voti0)}, id(voti1) : {id(voti1)}")
Se uniamo due dizionari che non hanno chiavi in comune il risultato è la somma dei due risultati. Se ci sono chiavi in comune, i valori saranno quelli dell'ultimo dizionario. Un esempio per chiarire:
d1 = {'a': 1, 'b': 2}
d2 = {'c': 3, 'd': 4}
d2a = {'b': 20, 'c': 3, 'd':4}
d1
e d2
non hanno chiavi comuni:
d_unione1 = {**d1, **d2}
d_unione1
d1
e d2a
hanno in comune la chiave 'b'
. Se uniamo d2a
a d1
la chiave 'b'
viene associata al suo valore in d2a
.
d_unione2 = {**d1, **d2a}
d_unione2
Se uniamo d1
a d2a
la chiave 'b'
viene associata al suo valore in d1
.
d_unione3 = { **d2a, **d1}
d_unione3
*args
+ **kwargs
¶Nella chiamata a una funzione tutti gli argomenti posizionali devono precedere gli argomenti con nome: *args,**kwargs
e NON **kwargs,*args
def genfun(*args,**kwargs):
for i,arg in enumerate(args):
print('index:', i,'value:',arg)
print('---------------')
for (key,value) in kwargs.items():
print('key:',key,' ','value:',value)
# Argomenti
positional_args = (10,20,30)
keyword_args = {'pippo':'cane', 'minnie':'topo'} # Le chiavi devono obbligatoriamente essere stringhe
# Chiamata
genfun(*positional_args,**keyword_args)
Per approfondire: https://realpython.com/python-kwargs-and-args/
In Python, gli oggetti sono passati per referenza (tipo pointer) all'oggetto. A seconda di come la referenza viene usate nella funzione e del tipo di oggetto che viene referenziato, questo può significare che qualsiasi modifica, all'interno della funzione, dell'oggetto passato si riflette immediatamente all'esterno.
Tre esempi per chiarire. Iniziamo passando una lista a una funzione che itera sugli elementi della sequenza raddoppiando il valore di ciascuno:
def double_the_values(l):
print(f"in double_the_values: l = {l}")
for i in range(len(l)):
l[i] = l[i] * 2
print(f"in double_the_values: changed l to l = {l}")
l_global = [0, 1, 2, 3, 10]
print(f"In main: s = {l_global}")
double_the_values(l_global)
print(f"In main: s = {l_global}")
Quando la funzione viene chiamata con argomento pippo
, le viene passata una referenza all'oggetto pippo
che deve essere definito all'esterno della funzione. l
diventa sinonimo di pippo
. La linea l[i] = l[i] * 2
calcola il membro di destra leggendo l'elemento di indice i
e poi moltiplicandolo per due. Una referenza a questo nuovo oggetto viene immagazzinata
nell'oggetto lista l
alla posizione di indice i
. È stato quindi modificato l'oggestto lista, che è referenziato attraverso il nome l
.
La referenza all'oggetto lista non cambia mai: la linea l[i] = l[i] * 2
cambia l'elemento l[i]
della lista l
ma non cambia mai la referenza l
per la lista. Quindi sia la funzione che il programma che la chiama operano sullo stesso oggetto, rispettivamente attraverso la referenza l
e global_l
.
Al contrario, nell'esempio seguente la lista globale non viene modificata all'interno della funzione:
def double_the_list(l):
print(f"in double_the_list: l = {l}")
l = l + l
print(f"in double_the_list: changed l to l = {l}")
l_global = "Hello"
print(f"In main: s = {l_global}")
double_the_list(l_global)
print(f"In main: s = {l_global}")
Quello che suucede in questo caso è che durante la valutazione di l = l + l
viene creato un nuovo oggetto che contiene l + l
, a cui successivamente legato il nome l
. In questo processo, si perde la referenza all'oggetto lista l
che è stato passato alla funzione che quindi non viene modificata.
Questo è vero anche per un oggetto mutabile come una lista.
l_global = [1,2]
print(f"In main: s = {l_global}")
double_the_list(l_global)
print(f"In main: s = {l_global}")
Infine, vediamo che output produce il programma che segue:
def double_the_value(l):
print(f"in double_the_values: l = {l}")
l = 2 * l
print(f"in double_the_values: changed l to l = {l}")
# 42 è immutabile
l_global = 42
print(f"In main: s = {l_global}")
double_the_value(l_global)
print(f"In main: s = {l_global}")
Anche in questo esempio, raddoppiamo il valore (da 42 a 84) all'interno della funzione. Tuttavia, quando colleghiamo l'oggetto 84 al nome python l
(nella riga l = l * 2
) abbiamo creato un nuovo oggetto (84), e poi leghiamo il nuovo oggetto a l
. Nuovamente, In questo processo, si perde la referenza all'oggetto 42 all'interno della funzione. Questo non modifica l'oggetto 42 in sè, né la sua referenza l_global
.
In conclusione, il comportamento di Python per quanto riguarda gli argomenti passati a una funzione può sembrare diverso nei vari casi. Tuttavia, si tratta sempre di chiamata per referenza e il comportamento può essere spiegato in ogni caso con lo stesso ragionamento.
Le funzioni sono oggetti in Python, quindi è possibile passare funzioni come argomenti ad altre funzioni.
def sum(a,b):
return a+b
def product(a,b):
return a*b
def operation(f,a,b):
return f(a,b)
operation(sum,3,6)
operation(product,3,6)
I Moduli
Raggruppano funzionalità
Forniscono namespaces (Insieme di simboli riconosciuti dal kernel)
La standard library di Python contiene aun gran numero di moduli - “Pronti per l'uso”
Provate a digitare help(’modules’)
Forniscono il modo per estendere Python
import math
Questo introduce il nome math
nel namespace del processo in cui il comando import è stato eseguito. I nomi delle funzioni contenute nel modulo math
non vengono introdotti nel namespace: devono essere invocati attraverso il nome math
. Per: math.sin
.
import math, cmath
Si può importare più di un modulo con un solo comando, anche se la Python Style Guide raccomanda di non farlo. È preferibile scrivere:
import math
import cmath
import math as mathematics
Il nome con cui un modulo è conosciuto localmente può essre diverso dal suo nome “ufficiale”. In genere lo si fa
Per evitare conflitti conflitti con nomi già esistenti
Per cambiare il nome ufficiale in uno più maneggevole. Per esempio import SimpleHTTPServer as shs
. Questa pratica viene scoraggiata per il production code (in genere nomi "descrittivi" rendono i programmi molto più comprensibili di nomi brevi e criptici), ma nella fase di esplorazione e test, utilizzare sinonimi brevi ci semplifica la vita
Esempi tipici e ormai universali sono import numpy as np
, import matplotlib.pyplot as plt
.
from math import sin
Questo comando importa la funzione sin
dal modulo math
, ma non introduce il nome math
nel namespace. Introduce solo il nome sin
. È possibile importare più nome con un solo comando:
from math import sin, cos
Dopo questo import
la funzione sin
può essere chiamata nel modo seguente:
sin(1)
Si noti che anche in Numpy c'è la funzione sin
. Se questa venisse importata con from numpy import sin
maschererebbe la funzione importata in precedenza da math
. Provate a farlo e poi eseguite help(sin)
.
Per concludere, guardiamo questa notazione:
from math import *
Di nuovo, questo comando non introduce il nome math
nel namespace. Introduce tuttavia nel namespace tutti i nomi pubblici contenuti nel modulo math
. In genere, è una pessima idea:
Un gran numero di nuovi nomi viene scaricato nel namespace attuale.
Siete sicuro che nessuno dei nuovi nomi sostituisca un nome già presente?
Diventa molto difficile tenere traccia della provenienza di tutti questi nomi
Detto questo, alcuni moduli (compreso qualcuno nella standard library), raccomandano di essere importati in questo modo. Usate con cautela!
Un modulo non è altro che un file Python. Ecco un esempio di modulo che potete salvare in un file chiamato module1.py
:
def someusefulfunction():
pass
print("My name is", __name__)
Possiamo eseguire questo (modulo) file come un normale programma Python (per esempio python module1.py
):
%pwd
%cd ../ShellPrograms/
%ls
!python module1.py
Notiamo che la variabile "magica" di Python __name__
prende il valore __main__
quando il file module1.py
viene eseguito.
D'altra parte, possiamo importare module1.py
in un altro file (che potrebbe chiamarsi prog1.py
), in questo modo:
import module1 #nel file prog1.py
Provate a fare queste operazioni per conto vostro, nella vostra home directory, invece che all'interno del notebook!
Sneak peek: dall'interno del notebook si fa come segue:
!python prog1.py
Quando Python incontra il comando import module1
in prog1.py
, cerca il file module1.py
nella working directory attuale (se non la trova cerca in tutte le directory in sys.path
), e apre il file module1.py
. Mentre legge il file module1.py
da cima a fondo, aggiunge qualsiasi definizione di funzione contenuta nel file all'interno del namespace nel contesto da cui module1.py
è stato chiamato (In questo caso il programma principale in prog1.py
). In questo esempio,c'è solo la funzione someusefulfunction
. Quando il processo di import process è completato, possiamo utilizzare module1.someusefulfunction
in prog.py
. Se Python incontra comandi diversi da definizione di funzioni (e classi) nell'importare module1.py
, li esegue immediatamente. In questo caso, trova il comando print(My name is, __name__)
.
Notate l'output diverso se importiamo module1.py
piuttosto che eseguirlo da solo: se il file viene importato, __name__
all'interno del modulo prende come valore il nome del modulo stesso.
import importlib.util
spec = importlib.util.spec_from_file_location("module1", "/Users/maina/python/MyCourse2/ShellPrograms/module1.py")
foo = importlib.util.module_from_spec(spec)
spec.loader.exec_module(foo)
Riassumendo,
__name__
vale __main__
se il file viene eseguito da solo
__name__
vale il nome del modulo (cioè il nome del file che contiene il modulo senza il suffisso .py
) se il modulo viene importato.
È possibile quindi utilizzare la costruzione if
seguente in module1.py
per scrivere del codice che viene eseguito soltanto quando il modulo viene eseguito da solo. Questo è utile per includere programmi di test o esemplificazioni delle capacità di un modulo nella parte "sotto condizione" del programma principale. È pratica comune che qualunque
file contenente un modulo contenga anche un programma principale all'interno dell'if
che mostra come utilizzare il modulo e quali capacità fornisca.
Il prossimo esempio mostra un main program nel modulo vectools.py
che dimostra l'uso delle funzioni definite nel file:
import numpy as np
def norm(x):
"""returns the magnitude of a vector x"""
return np.sqrt(np.sum(x ** 2))
def unitvector(x):
"""returns a unit vector x/|x|. x needs to be a numpy array."""
xnorm = norm(x)
if xnorm == 0:
raise ValueError("Can't normalise vector with length 0")
return x / norm(x)
if __name__ == "__main__":
#a little demo of how the functions in this module can be used:
x1 = np.array([0, 1, 2])
print("The norm of " + str(x1) + " is " + str(norm(x1)) + ".")
print("The unitvector in direction of " + str(x1) + " is " \
+ str(unitvector(x1)) + ".")
Se questo file viene eseguito con python vectools.py
, allora __name__==__main__
è vero, e l'output sarà:
!pwd
!python ../ShellPrograms/vectools.py
Se il file viene importato (cioè usato come un modulo) in un altro file python, allora __name__==__main__
è falso, e i comandi all'interno dell'if
non viene eseguito (e nessun output viene prodotto).
Anche se un programma Python non è concepito per essere usato come un modulo, è buona abitudine (e di uso comune) scrivere il programma principale all'interno della condizione if __name__ == "__main__"
:
capita spesso che le funzioni contenute nel file possano essere riutilizzate (risparmiando lavoro). Notate che questo incoraggia fortemente a spezzare i programmi in funzioni.
è utile per il "regression testing", il controllo che modifiche ad un codice funzionante non introducano errori. Si effettua eseguendo dei programmi di test che in precedenza funzionavano correttamente
Supponiamo di dover scrivere una funzione che restituisca i cinque più piccoli numeri primi, e che inoltre, li stampi. (Il compito in questo caso è banale, ma possiamo immaginare situazioni più complesse). Si potrebbe essere tentati di scrivere:
def primes5():
return (2, 3, 5, 7, 11)
for p in primes5():
print(f"{p}", end=' ')
È però preferibile racchiudere la funzione principale sotto condizione:
def primes5():
return (2, 3, 5, 7, 11)
if __name__=="__main__":
for p in primes5():
print(f"{p}", end=' ')
In Molti modi per calcolare una somma trovate altri esempi di questa tecnica. Includere funzioni con nomi che iniziano con test_
rende possibile utilizzare ilframework di regression testing py.test (see http://pytest.org/).
Per lanciare lo shell di Python, dalla linea di comando eseguire (assumendo che il "prompt" di sistema sia "$"):
$ python <Return>
La risposta dovrebbe essere simile a:
Python 3.7.1 (default, Dec 14 2018, 13:28:58)
[Clang 4.0.1 (tags/RELEASE_401/final)] :: Anaconda, Inc. on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
">>>" è il "prompt" di Python in modalità interattiva.
Python è un linguaggio interpretato.
I comandi vengoni immediatamente interpretati ed eseguiti dal Python interpreter (Interactive Mode). Questo è molto utile per lo studente/ programmatore per capire come usare un certo comando (magari prima di inserire il comando in un programma Python complesso). Il modo di procedere di Python può essere descritto come:
Leggere il comando --> Eseguirlo --> Stampare il risultato ottenuto --> ripetere il ciclo(Loop)
in inglese REPL (Read-Execute-Print-Repeat).
È possibile digitare un comando e eseguirlo premendo il tasto Invio:
2 + 2 4
Possiamo raccogliere una serie di comandi in file di testo e salvarlo su disco come un Python program (Normal Mode). Convenzionalmente questi file hanno l'estensione“.py
”, per esempio hello.py
.
Per eseguire lo script pippo.py in modalità interattiva si può dare il comando:
>>> import pippo <Return>
Per uscire dalla modalità interattiva digitare:
>>> quit() <Return>
Per eseguire lo script pippo.py dalla linea di comandi, senza attivare la modalità interattiva, si può dare il comando:
$ python pippo.py <Return>