Vai al contenuto

Moduli Python

Il capitolo sui moduli Python è probabilmente il più importante di tutti! Python è diventato uno dei linguaggi più utilizzati al mondo proprio perché grazie ai suoi moduli può arrivare in maniera veloce a gestire qualunque aspetto della programmazione: basta scegliere il modulo giusto!!!

Per organizzare al meglio il lavoro, l'argomento Moduli Python è spalmato su 3 capitoli:

  1. In questo capitolo vedremo come si crea un modulo e come si utilizza.

  2. Nel capitolo Python Standard Library andremo a vedere i moduli predefiniti, ovvero quelli già disponibili senza fare null'altro in qualunque installazione Python: in questo caso il focus si sposta sull'utilizzo di moduli prodotti da altri, sulla consultazione della documentazione, sull'utilizzo dei moduli Python di base.

  3. Nel capitolo PyPi ci occuperemo di reperire, installare e utilizzare alcuni dei moduli disponibili su Internet, precisamente nel Python Package Index, per ottenere in un attimo uno o più moduli che implementano una funzionalità per noi interessante e/o utile per il software che vogliamo sviluppare.

Al lavoro!

Come accennato, in questo capitolo ci occuperemo di implementare un modulo scrivendo tutto il codice, investigando i concetti di base e procedendo ai test semplici delle funzionalità implementate.

Istruzione import

In Python la clausola import si utilizza per rendere disponibile il codice in un modulo in un altro. Questo concetto rende semplice l'organizzazione del codice in senso logico, sia dal punto di vista dei file che dal punto di vista dell'organizzazione globale del codice.

Dal punto di vista dei file, ogni modulo è ben organizzato perché contiene codice coerente: un ipotetico modulo numeri conterrà funzioni che lavorano coi numeri, un modulo statistiche contiene funzioni per il calcolo statistico, etc..

Dal punto di vista dell'organizzazione globale del codice, l'importazione di un modulo genererà un namespace che manterrà semplice la gestione del codice.

Capisco che il secondo punto di vista necessita di un ulteriore chiarimento: dal punto di vista intuitivo, noi abbiamo già usato il modulo random per le nostre liste numeriche. Allora scriviamo:

import random

# per accedere alle funzioni del modulo random, dobbiamo scrivere modulo.funzione
intero = random.randint(1,100)

Questa organizzazione imposta dall'import (ovvero modulo.funzione) permette di capire chiaramente che la funzione randint deriva dal modulo random e non è stata definita all'interno di questo modulo. Quando le importazioni saranno di più e i moduli scritti da noi più lunghi, questa cosa inizierà ad essere molto utile.

Inoltre il namespace indotto preserva le funzioni importate da un altro importante problema: se molte persone diverse scrivono codice potrebbe tranquillamente capitare che due funzioni scritte da persone diverse, su moduli diversi, abbiano lo stesso nome. Il namespace impedisce confusione in questo caso.

Nomi dei moduli

Tutte le persone hanno un nome che le identifica (ad esempio il mio nome è Andrea), ma quando le persone parlano di se stesse usano la parola io (io mi chiamo Andrea). E fin qui mi sembra tutto semplice.

Abbiamo detto finora (e nel dubbio, lo ripeto con chiarezza) che anche i moduli, ovvero i file Python con estensione .py, hanno un nome, che dipende dal nome del file stesso: ad esempio, il file pippo.py definisce il modulo di nome pippo. Ogni volta che definite un modulo, Python inizializza alcune variabili speciali, fra cui la variabile __name__ che contiene il nome del modulo (fra un attimo faccio un esempio).

Così come le persone, parlando di se stesse, dicono io, i moduli, al loro interno non utilizzano il loro nome, ma utilizzano la parola __main__.

Cerchiamo di chiarire subito questo primo concetto, il resto verrà naturalmente di conseguenza:

Esempio 1: un unico file
# file pippo.py

# la variabile __name__ si riferisce al nome del modulo pippo
# essendo al suo interno, conterrà la parola "__main__"
print(__name__)
Esempio 2: un altro file
# file ciccio.py

# importo il modulo pippo, dell'esempio 1
# importando il modulo, andrò ad eseguirlo. la print sopra (eseguita all'esterno del modulo pippo)
# scriverà il suo nome: "pippo".
import pippo

# la variabile __name__ qui sotto si riferisce al nome del modulo ciccio
# essendo al suo interno, conterrà la parola "__main__"
print(__name__)

# la variabile __name__ qui sotto si riferisce al nome del modulo pippo
# NON essendo dentro al modulo pippo, conterrà il suo nome proprio, ovvero "pippo"
print(pippo.__name__)

Tutto qui 😄

Testing

Dalle considerazioni fatte nei precedenti capitoli, vorrei farvi capire una tecnica molto utilizzata in ambito Python per l'esecuzione dei moduli e i test delle funzioni in essi contenute.

La strategia è questa:

  1. scrivo il modulo con la definizione delle funzioni che mi interessano
  2. faccio delle prove per assicurarmi che tutto... funzioni! (faccio i test!!!) in fondo al file, dentro un if __name__ == "__main__"
  3. Se eseguo direttamente il modulo con le funzioni, l'if mi permette di svolgere i test (e verificare che tutto funzioni correttamente)
  4. Se importo il modulo in un altro contesto, il suo nome NON sarà "main" e i test non saranno svolti, permettendo di importare solo le funzioni!

Da questa spiegazione, deriva questa modalità di scrittura dei moduli con funzioni:

# file prova.py

def molt(fatt1:float,fatt2:float) -> float:
    """
    prende i valori nei parametri fatt1,fatt2 e ritorna il prodotto fatt1 * fatt2 

    Ad esempio: molt(4,5) ritorna 20

    Parametri
    ---------
    fatt1
        un numero reale, il primo fattore della moltiplicazione
    fatt2
        un numero reale, il secondo fattore della moltiplicazione
    """
    return fatt1 * fatt2

# questa parte NON sarà eseguita all'esterno del modulo "prova"
if __name__ == "__main__":
    x = 5
    y = 4
    print("x:",x)
    print("y:",y)
    print( f"molt({x},{y}):{molt(x,y)}" )
    # ... e così via...

Da ora in poi scriveremo così i nostri moduli e in questo modo faremo tutti gli esercizi che seguono. Questo ci permetterà di definire un modus operandi logico e professionale, che ci tornerà enormemente utile quando le cose diventeranno inevitabilmente più complicate.

Adesso, sotto con gli esercizi!

Esercizi

Nota

Ognuno dei seguenti esercizi deve essere implementato in un unico file, nominato con il nome indicato nell'intestazione dell'esercizio, completo di documentazione e di (almeno) 3 casi di test per ognuna delle funzioni implementate!!!

Esercizio 601: modulo "SequenzeNumeriche"

minoriDiUnElemento
Creare una funzione che, data una sequenza numerica (tupla o lista) e un numero qualsiasi, conta quanti numeri nella sequenza sono minori del numero.

sommaElementi
Creare una funzione che, data una sequenza numerica (tupla o lista), restituisce la somma dei numeri della sequenza.

mediaElementi
Creare una funzione che, data una sequenza numerica (tupla o lista), restituisce la media aritmetica dei numeri della sequenza.


Esercizio 602: modulo "ManipolazioneStringhe"

lunghezzaStringa
La funzione prende una stringa come parametro e ritorna il numero di caratteri di cui è composta (la sua lunghezza)

contaLettera
La funzione prende una stringa e una lettera come parametro e ritorna il numero di volte in cui la lettera è presente all'interno della stringa

tutteMaiuscole
La funzione prende una stringa come parametro e ritorna la stringa trasformata in maiuscolo

tutteMinuscole
La funzione prende una stringa come parametro e ritorna la stringa trasformata in minuscolo

invertiMaiuscoleMinuscole
La funzione prende una stringa come parametro e ritorna la stringa con maiuscole e minuscole invertite.

inizialiMaiuscole
La funzione prende una stringa come parametro e ritorna la stringa trasformata in minuscolo con le iniziali di ogni parola maiuscole


Esercizio 603: modulo "PianoCartesiano"

quadrante
La funzione prende due numeri x, y che rappresentano le coordinate del punto P nel piano cartesiano e restituisce un numero corrispondente al quadrante nel quale esso si trova, ovvero un numero fra 1 e 4. Restituisce 0 nel caso che il punto sia su uno degli assi cartesiani.

distanzaOrigine
La funzione prende due numeri x, y che rappresentano le coordinate del punto P nel piano cartesiano e restituisce la distanza del punto P dall'origine degli assi, ovvero dal punto O di coordinate (0,0).

distanzaFraDuePunti
La funzione prende quattro numeri xP, yP, xQ, yQ che rappresentano le coordinate dei punto P e Q nel piano cartesiano e restituisce la distanza fra loro.


Esercizio 604: modulo "CifreNumeriche"

unità
La funzione prende un numero come parametro e restituisce la cifra delle unità. Ad esempio unita(23) restituisce 3, unita(8174.56) restituisce 4.

decine
La funzione prende un numero come parametro e restituisce la cifra delle decine. Ad esempio decine(23) restituisce 2, decine(8174.56) restituisce 7.

centinaia
La funzione prende un numero come parametro e restituisce la cifra delle centinaia. Ad esempio centinaia(23) restituisce 0, centinaia(8174.56) restituisce 1.

migliaia
La funzione prende un numero come parametro e restituisce la cifra delle migliaia. Ad esempio migliaia(23) restituisce 0, migliaia(8174.56) restituisce 8.

decimi
La funzione prende un numero come parametro e restituisce la cifra dei decimi. Ad esempio decimi(23) restituisce 0, decimi(8174.56) restituisce 5.

centesimi
La funzione prende un numero come parametro e restituisce la cifra dei centesimi. Ad esempio centesimi(23) restituisce 0, centesimi(8174.56) restituisce 6.


Esercizio 605: modulo "FunzioniNumeriche"

sommaDivisori
La funzione prende un numero intero come parametro e restituisce la somma dei suoi divisori propri (ovvero dei divisori minori del numero stesso)

listaDivisori
La funzione prende un numero intero come parametro e restituisce la lista dei suoi divisori propri (ovvero dei divisori minori del numero stesso)

isPrime
La funzione prende un numero intero come parametro e restituisce True se il numero è primo, false altrimenti. (sugg: se un numero è primo il suo unico divisore proprio è 1...)

perfetto
La funzione prende un numero intero come parametro e restituisce True se il numero è perfetto, False altrimenti (un numero si dice perfetto se e solo se è uguale alla somma dei suoi divisori propri)

nthPrime
La funzione prende un numero intero come parametro e restituisce l'ennesimo numero primo. Ad esempio dato 3, la funzione restituisce 5 perché, essendo i numeri primi 2, 3, 5, 7, 11, etc... 5 è il terzo numero primo.

semiPrimo
La funzione prende un numero intero come parametro e restituisce True se il numero è semiprimo, False altrimenti. Un numero si dice semiprimo se è esprimibile come prodotto di due numeri primi. Ad esempio 6 = 2 per 3 è semiprimo, 7 = 7 per 1 non è semiprimo, infatti 1 non è primo.