3 ** 4
81
I Large Language Models (LLM), come GPT-4 o Claude, stanno rivoluzionando non solo la generazione di testo in linguaggio naturale, ma anche il campo della programmazione e dell’analisi dei dati. La programmazione, con le sue regole esplicite e la sintassi strutturata, si adatta particolarmente bene alle capacità di riconoscimento dei pattern degli LLM. A differenza dei linguaggi umani, ricchi di ambiguità ed espressioni idiomatiche, i linguaggi di programmazione sono rigorosi e precisi.
Nel contesto dell’analisi dei dati, è naturale combinare le capacità degli LLM con la potenza computazionale di linguaggi statistici come R o di programmazione come Python. Sono già stati pubblicati libri su come integrare le capacità degli LLM con l’analisi dei dati (Matter, 2025), e sta emergendo una nuova branca dell’ingegneria dedicata allo sviluppo dei prompt più efficaci per l’uso con gli LLM. Pertanto, il problema non è se utilizzare gli LLM a supporto della programmazione, ma come farlo nel modo più efficace.
Gli LLM possono essere utilizzati per risolvere vari problemi di programmazione in Python o R, basandosi su richieste formulate in linguaggio naturale. Questi modelli sono in grado di generare codice, identificare errori negli script, ottimizzare il codice, generare documentazione e creare casi di test, anche per problemi di programmazione complessi. L’avvento degli LLM ha cambiato radicalmente il modo in cui sia i principianti sia i professionisti si approcciano alla programmazione. Tuttavia, ciò non significa che gli LLM abbiano completamente sostituito la scrittura di codice da parte degli esseri umani. Piuttosto, gli LLM sono strumenti che possono migliorare notevolmente il lavoro di chi ha già una buona conoscenza delle regole sintattiche e dei principi di programmazione.
Un principio fondamentale da tenere a mente è che maggiore è la conoscenza delle regole sintattiche di un linguaggio di programmazione, maggiore sarà l’efficacia dell’uso degli LLM per gli scopi dell’analisi dei dati. Anche se i problemi di programmazione trattati in questo corso sono relativamente semplici rispetto alle capacità degli LLM, gli utenti che sfrutteranno questi strumenti trarranno sicuramente vantaggio dalla comprensione delle regole sintattiche del linguaggio di programmazione utilizzato, principalmente Python (con alcuni esempi anche in R).
Gli LLM si basano sul concetto di “predizione del prossimo token”. Questo significa che sono addestrati per prevedere la parola o il carattere successivo in una sequenza, basandosi sui precedenti. Questo principio è alla base della capacità degli LLM di generare codice e di migliorare i flussi di lavoro dell’analisi dei dati in Python o R. Oltre a miliardi di parole provenienti da testi comuni (siti web, libri, articoli di riviste), gli LLM di OpenAI, come GPT-4, sono stati addestrati su un vasto corpus di codice open-source e discussioni sul codice presenti su piattaforme come Stack Overflow. Questo consente loro di generare codice sintatticamente e semanticamente corretto in molte situazioni.
Acquisire competenze sull’uso pratico dell’AI/LLM nella programmazione e nell’analisi dei dati è più di una tendenza: è una necessità. I professionisti che sanno utilizzare efficacemente l’AI/LLM per migliorare le loro competenze di programmazione e integrare questi strumenti in Python e R hanno un vantaggio competitivo. Con la crescente diffusione dell’AI e degli LLM nell’industria e nel mondo accademico, la domanda per queste competenze continuerà a crescere.
L’obiettivo di questa sezione della dispensa è fornire una panoramica della sintassi di Python e delle principali funzioni di pacchetti come Pandas, Numpy e Matplotlib, utili per la data science. Python è un linguaggio di programmazione versatile e di facile lettura, adatto a numerosi usi. Anche se il suo nome è un omaggio al gruppo comico Monty Python, apprendere Python richiede tempo, pratica e impegno.
Questa guida (e l’intero corso) si concentra sull’insegnamento dei principi fondamentali della programmazione e dell’analisi dei dati psicologici, piuttosto che sui dettagli tecnici. Facendo riferimento alla distinzione di Marr tra livello computazionale e livello algoritmico (Marr, 2010), una spiegazione a livello computazionale descrive la logica del problema e i passaggi necessari per trasformare l’input in output, mentre il livello algoritmico specifica come eseguire queste operazioni logiche utilizzando la sintassi di un particolare linguaggio di programmazione.
Il focus di questo corso è il livello computazionale, poiché è fondamentale per comprendere i problemi e progettare soluzioni. Con questa conoscenza, gli studenti saranno più capaci di risolvere problemi specifici e di cercare autonomamente la sintassi appropriata per il problema da risolvere.
Nell’era dell’intelligenza artificiale, è essenziale sviluppare la capacità di pensare in modo computazionale. Questa competenza va oltre la mera programmazione e offre un approccio strutturato per risolvere problemi complessi. Sebbene i modelli di linguaggio avanzati (LLM) siano in grado di risolvere molti dei problemi di programmazione affrontati in questo corso, una comprensione approfondita dei principi della programmazione rimane cruciale per interpretare, modificare o migliorare le soluzioni proposte da tali sistemi.
In conclusione, nonostante i notevoli progressi dell’AI, una solida conoscenza della programmazione rimane indispensabile. Padroneggiare i principi fondamentali della codifica è essenziale per sfruttare al massimo strumenti avanzati come i modelli linguistici di grandi dimensioni (LLM) e per affrontare con efficacia le sfide legate alla programmazione e all’analisi dei dati.
I programmi sono costituiti da insiemi di espressioni che elaborano dati per fornire istruzioni specifiche al computer. In Python, ad esempio, l’operazione di moltiplicazione viene eseguita utilizzando l’asterisco (*
) tra due numeri. Quando il programma incontra un’espressione come 3 * 4
, il computer la valuta e restituisce il risultato, che può essere visualizzato, ad esempio, in una cella successiva di un notebook Jupyter.
Le regole sintattiche di un linguaggio di programmazione come Python sono rigorose. Per esempio, non è consentito inserire due simboli di moltiplicazione (*
) consecutivi senza un operando tra di essi. Se un’espressione viola queste regole sintattiche, Python restituirà un “SyntaxError”, un messaggio che segnala la non conformità alle regole del linguaggio. Ad esempio:
3 * * 4
restituirà:
Cell In[3], line 1
3 * * 4
^
SyntaxError: invalid syntax
Anche piccole variazioni all’interno di un’espressione possono cambiarne completamente il significato. Nell’esempio seguente, lo spazio tra i due asterischi è stato rimosso. Ora, poiché gli asterischi compaiono correttamente tra due numeri, l’espressione diventa sintatticamente valida e indica l’elevamento a potenza: 3 ** 4
equivale a 3 elevato alla quarta potenza (\(3 \times 3 \times 3 \times 3\)). In programmazione, simboli come *
e **
sono chiamati “operatori”, mentre i valori su cui agiscono sono detti “operandi”.
3 ** 4
81
La tabella seguente elenca i principali operatori binari utilizzati in Python, chiamati così perché agiscono su due operandi.
Operazione | Operatore |
---|---|
addizione | + |
sottrazione | - |
moltiplicazione | * |
divisione (reale) | / |
divisione (intera; rimuove il resto) | // |
resto (modulo) | % |
elevamento a potenza | ** |
Le due operazioni che potrebbero essere meno familiari sono %
(trova il resto di una divisione) e //
(esegui una divisione scartando il resto).
Per esempio, la divisione intera (scartando il resto) di 11/2 produce 5.
11 // 2
5
Il resto di 11/2 è 1.
11 % 2
1
Usando gli operatori che abbiamo elencato in precedenza possiamo usare Python come un calcolatore.
print("4 + 2 è", 4 + 2)
print("4 - 2 è", 4 - 2)
print("4 * 2 è", 4 * 2)
print("4 / 2 è", 4 / 2)
print("4 ** 2 è", 4**2)
print("9 % 4 è", 9 % 4)
print("9 // 4 è", 9 // 4)
4 + 2 è 6
4 - 2 è 2
4 * 2 è 8
4 / 2 è 2.0
4 ** 2 è 16
9 % 4 è 1
9 // 4 è 2
L’applicazione degli operatori aritmetici in Python dipende dalle seguenti regole di precedenza degli operatori, che sono analoghe a quelle usate in algebra.
1 + 2 * 3 * 4 * 5 / 6 ** 3 + 7 + 8 - 9 + 10
17.555555555555557
1 + 2 * (3 * 4 * 5 / 6) ** 3 + 7 + 8 - 9 + 10
2017.0
Quando un risultato viene generato, viene visualizzato ma non memorizzato da nessuna parte, come visto negli esempi precedenti. Per poter riutilizzare quel risultato in seguito, è necessario salvarlo in un oggetto a cui possiamo dare un nome. Questo oggetto prende il nome di variabile.
Per creare una variabile, utilizziamo un’istruzione di assegnazione. Un’istruzione di assegnazione consiste nel dare un nome alla variabile, seguito dal simbolo di uguale (=
), e dall’espressione il cui valore vogliamo memorizzare. Il processo di assegnazione associa il valore dell’espressione a destra del simbolo di uguale al nome specificato a sinistra. Da quel momento, ogni volta che utilizziamo quel nome in un’espressione, verrà utilizzato il valore associato a esso durante l’assegnazione.
= 10
a = 20
b + b a
30
= 1/4
a = 2 * a
b b
0.5
= 100
my_var = 3
const
* const my_var
300
In Python, ogni “oggetto” è un’area di memoria nel computer. Una “variabile” funge da etichetta che fa riferimento a quest’area. Se un oggetto non ha più etichette (ovvero, non ci sono più variabili che lo referenziano), i dati contenuti nell’oggetto diventano inaccessibili. Il Garbage Collector del linguaggio si occuperà di rilevare questi oggetti non referenziati e liberare la memoria, permettendo che venga riutilizzata per nuovi dati.
Quando scriviamo programmi in Python, è importante scegliere nomi significativi per le variabili, poiché queste ci permettono di memorizzare e manipolare dati nel corso dell’esecuzione del programma. I nomi delle variabili devono rispettare alcune regole specifiche per essere validi all’interno del linguaggio.
In Python, i nomi delle variabili possono contenere caratteri alfanumerici (da a-z, A-Z, 0-9) e il carattere speciale _
(underscore). Tuttavia, devono iniziare con una lettera (a-z, A-Z) o con un underscore, ma non con un numero. Gli spazi non sono permessi all’interno dei nomi delle variabili, quindi l’underscore è comunemente usato per separare le parole all’interno di un nome (ad esempio, nome_variabile
).
Per facilitare la leggibilità del codice, è buona pratica seguire alcune convenzioni: i nomi delle variabili iniziano generalmente con una lettera minuscola, mentre i nomi delle classi cominciano con una lettera maiuscola.
È importante notare che in Python esiste una serie di parole riservate, chiamate parole chiave (keywords), che non possono essere utilizzate come nomi di variabili. Queste parole chiave hanno un significato speciale all’interno del linguaggio e sono essenziali per la sintassi di Python. Queste parole chiave sono:
import keyword
print(*keyword.kwlist, sep="\n")
False
None
True
and
as
assert
async
await
break
class
continue
def
del
elif
else
except
finally
for
from
global
if
import
in
is
lambda
nonlocal
not
or
pass
raise
return
try
while
with
yield
Si presti attenzione alla parola chiave “lambda”, che potrebbe facilmente essere un nome di variabile naturale in un programma scientifico. Tuttavia, essendo una parola chiave, non può essere utilizzata come nome di variabile.
Utilizzare nomi di variabili chiari e appropriati aiuta a rendere il codice più leggibile e comprensibile, sia per il programmatore che per chiunque altro debba interagire con il codice.
In Python, le variabili possono appartenere a diverse tipologie di dati, ciascuna con caratteristiche e utilizzi specifici.
Le stringhe sono sequenze di caratteri, utilizzate per rappresentare testo. In Python, le stringhe possono essere create utilizzando apici singoli (' '
), doppi (" "
) o tripli (''' '''
oppure """ """
) per delimitare il testo. Esempi di stringhe sono:
"Hello, world!"
'Beyonce-Lemonade.txt'
"lemonade"
Si noti il risultato ottenuto quando si applica l’operatore +
a due stringhe.
"data" + "science"
'datascience'
"data" + " " + "science"
'data science'
Sia le virgolette singole che doppie possono essere utilizzate per creare le stringhe: “ciao” e ‘ciao’ sono espressioni equivalenti. Tuttavia, le virgolette doppie sono spesso preferite poiché consentono di includere virgolette singole all’interno delle stringhe.
"Che cos'è una parola?"
"Che cos'è una parola?"
L’espressione precedente avrebbe prodotto un SyntaxError
se fosse stata racchiusa da virgolette singole.
In Python, una stringa è concepita come una sequenza ordinata di caratteri. Grazie all’operatore di indicizzazione, rappresentato dalle parentesi quadre []
, è possibile accedere a singoli elementi della stringa.
È importante ricordare che l’indicizzazione in Python parte da zero.
L’indice del primo carattere è [0]
, quello del secondo è [1]
, del terzo [2]
, e così via. Questa funzionalità consente di manipolare o consultare specifici segmenti della stringa, piuttosto che gestirla come un blocco unico.
Consideriamo questo verso di Eugenio Montale:
= "Tendono alla chiarità le cose oscure"
my_string print(my_string)
Tendono alla chiarità le cose oscure
0] my_string[
'T'
3] my_string[
'd'
len(my_string)
36
La stringa “my_string” conta 36 caratteri. Pertanto, gli indici validi per questa stringa vanno da 0 a 35. Per accedere all’ultimo carattere, è necessario utilizzare l’indice 35, che corrisponde a 36 meno 1.
35] my_string[
'e'
Un modo efficiente per ottenere l’ultimo carattere è ricorrere alla funzione len
, sottraendo 1 al risultato:
len(my_string) - 1] my_string[
'e'
Si noti che len()
è una funzione. Una funzione è un blocco di codice che esegue un’operazione specifica. I programmatori chiamano anche gli input delle funzioni “parametri” o “argomenti”.
La funzione len
prende un input e restituisce un output. L’output è la lunghezza di ciò che è stato passato come input.
Oltre a estrarre caratteri individuali da una stringa, Python offre la possibilità di selezionare segmenti di testo attraverso la tecnica dello “slicing”. Questo meccanismo è simile all’indicizzazione, ma utilizza due indici separati da un carattere a due punti (:). Il primo indice indica la posizione di partenza dello “slicing” nella stringa, mentre il secondo indice segnala il punto in cui terminare l’estrazione del segmento.
2:4] my_string[
'nd'
Se si omette il primo indice, Python utilizzerà l’inizio della stringa; se si omette il secondo, utilizzerà la fine della stringa.
4] my_string[:
'Tend'
4:] my_string[
'ono alla chiarità le cose oscure'
A partire da una stringa esistente, si possono generare nuove stringhe mediante l’utilizzo di metodi specifici per le stringhe. Questi metodi sono essenzialmente funzioni che agiscono direttamente sull’oggetto stringa. Per invocare un metodo, basta posizionare un punto subito dopo la stringa e seguire con il nome del metodo desiderato. Ad esempio, il metodo successivo converte tutti i caratteri della stringa in maiuscole.
my_string.upper()
'TENDONO ALLA CHIARITÀ LE COSE OSCURE'
Il metodo my_string.title()
è utilizzato per convertire la prima lettera di ogni parola nella stringa my_string
in maiuscolo, mentre rende tutte le altre lettere minuscole. In pratica, trasforma la stringa in una forma “a titolo”, in cui ogni parola inizia con una lettera maiuscola.
my_string.title()
'Tendono Alla Chiarità Le Cose Oscure'
I numeri interi rappresentano numeri senza una componente decimale. In Python, possono essere creati assegnando un valore senza parte decimale a una variabile. Esempio di un numero intero è:
= 20 age
I numeri in virgola mobile, o “float”, rappresentano numeri che hanno una componente decimale. Sono creati assegnando un valore con una parte decimale a una variabile. Esempio di un numero float è:
= 36.4 temperature
I numeri in virgola mobile, o “float”, hanno una precisione limitata a circa 15-16 cifre decimali; oltre questo limite, la precisione viene persa. Nonostante questa limitazione, sono sufficienti per la maggior parte delle applicazioni.
Inoltre, è possibile utilizzare la notazione scientifica per rappresentare numeri molto grandi o molto piccoli. In questa notazione, m * 10^n
viene comunemente abbreviato come mEn
, dove “E” rappresenta l’esponente dieci. Ad esempio, 1E9
equivale a un miliardo (\(1 \times 10^9\)) e 1E-9
rappresenta un miliardesimo (\(1 \times 10^{-9}\)).
I valori booleani possono assumere solo due stati: vero (True
) o falso (False
). Sono utilizzati per rappresentare le condizioni logiche e sono ottenuti attraverso espressioni di confronto. Esempio di un valore booleano è:
= False is_raining
Nel contesto delle operazioni aritmetiche, True
è equivalente al numero intero 1, mentre False
corrisponde a 0. Questo permette di includere valori booleani in calcoli matematici. Per esempio:
True + True + False
2
Un valore booleano viene ritornato quando si valuta un confronto. Per esempio:
3 > 1 + 1
Il valore True indica che il confronto è valido; Python ha confermato questo semplice fatto sulla relazione tra 3 e 1+1.
Si noti la regola di precedenza: gli operatori >, <, >=, <=, ==, != hanno la precedenza più bassa (vengono valutati per ultimi), il che significa che nell’espressione precedente viene prima valutato (1 + 1) e poi (3 > 2).
Un operatore di confronto è un operatore che esegue un qualche tipo di confronto e restituisce un valore booleano (True oppure False). Per esempio, l’operatore ==
confronta le espressioni su entrambi i lati e restituisce True
se hanno gli stessi valori e False
altrimenti. L’opposto di ==
è !=
, che si può leggere come ‘non uguale al valore di’. Gli operatori di confronto sono elencati qui sotto:
Confronto | Operatore |
---|---|
Minore | < |
Maggiore | > |
Minore o uguale | <= |
Maggiore o uguale | >= |
Uguale | == |
Non uguale | != |
Ad esempio:
= 4
a = 2
b
print("a > b", "is", a > b)
print("a < b", "is", a < b)
print("a == b", "is", a == b)
print("a >= b", "is", a >= b)
print("a <= b", "is", a <= b)
Nella cella seguente si presti attenzione all’uso di =
e di ==
:
= 10 == 20
boolean_condition print(boolean_condition)
L’operatore =
è un’istruzione di assegnazione. Ovvero, crea un nuovo oggetto. L’operatore ==
valuta invece una condizione logica e ritorna un valore booleano.
Un’espressione può contenere più confronti e tutti devono essere veri affinché l’intera espressione sia vera. Ad esempio:
1 < 1 + 1 < 3
Python è un linguaggio con tipizzazione dinamica, il che significa che il tipo di una variabile è determinato dal valore che le viene assegnato durante l’esecuzione del programma e non necessita di essere dichiarato esplicitamente.
Per identificare il tipo di una variabile o del risultato di un’espressione, Python mette a disposizione la funzione type()
. Questa funzione, quando chiamata con una variabile o un’espressione come argomento, restituisce il tipo di dati corrispondente.
Nell’esempio seguente il programma stamperà <class 'str'>
, indicando che x
è una variabile di tipo “stringa”.
= "hello"
x print(type(x))
<class 'str'>
Applichiamo la funzione type()
alle altre variabili che abbiamo definito in precedenza.
= 20
age print(type(age))
<class 'int'>
= 36.4
temperature print(type(temperature))
<class 'float'>
= False
is_raining print(type(is_raining))
<class 'bool'>
Gli operatori booleani (o operatori logici) confrontano espressioni (non valori) e ritornano un valore booleano. Python ha tre operatori logici:
and
– Ritorna True solo se entrambi le espressioni sono vere, altrimenti ritorna Falseor
– Ritorna True se almeno una delle due espressioni è vera, altrimenti ritorna False.not
– Ritorna True se l’espressione è falsa, altrimenti ritorna False.Ad esempio:
= 2
a = 3
b
+ b > a) and (a + b > b) (a
True
Nella cella sopra le parentesi tonde sono opzionali ma facilitano la lettura.
L’operatore and
restituisce True
solo se entrambe le condizioni booleane sono vere. Ad esempio, True and False
restituirà False
perché una delle condizioni è falsa:
True and False
False
L’operatore or
restituisce True
se almeno una delle due condizioni booleane è vera. Ad esempio, True or False
restituirà True
perché almeno una delle condizioni è vera.
True or False
True
L’operatore not
viene utilizzato per invertire il valore di verità di una condizione booleana. Ad esempio, not True
restituirà False
e not False
restituirà True
.
not True
False
Alcuni esempi sono i seguenti (si noti l’uso della funzione len()
):
print(3 > 2) # True, because 3 is greater than 2
print(3 >= 2) # True, because 3 is greater than 2
print(3 < 2) # False, because 3 is greater than 2
print(2 < 3) # True, because 2 is less than 3
print(2 <= 3) # True, because 2 is less than 3
print(3 == 2) # False, because 3 is not equal to 2
print(3 != 2) # True, because 3 is not equal to 2
print(len("mango") == len("avocado")) # False
print(len("mango") != len("avocado")) # True
print(len("mango") < len("avocado")) # True
print(len("milk") != len("meat")) # False
print(len("milk") == len("meat")) # True
print(len("tomato") == len("potato")) # True
print(len("python") > len("dragon")) # False
True
True
False
True
True
False
True
False
True
True
False
True
True
False
Altri esempi di come questi operatori possono essere utilizzati sono i seguenti:
print(3 > 2 and 4 > 3) # True - because both statements are true
print(3 > 2 and 4 < 3) # False - because the second statement is false
print(3 < 2 and 4 < 3) # False - because both statements are false
print("True and True: ", True and True)
print(3 > 2 or 4 > 3) # True - because both statements are true
print(3 > 2 or 4 < 3) # True - because one of the statements is true
print(3 < 2 or 4 < 3) # False - because both statements are false
print("True or False:", True or False)
print(not 3 > 2) # False - because 3 > 2 is true, then not True gives False
print(not True) # False - Negation, the not operator turns true to false
print(not False) # True
print(not not True) # True
print(not not False) # False
True
False
False
True and True: True
True
True
False
True or False: True
False
False
True
True
False
Abbiamo tralasciato alcuni operatori in Python. Due di quelli che abbiamo omesso sono gli operatori di appartenenza, in
e not in
. Gli altri operatori che abbiamo tralasciato sono gli operatori bitwise e gli operatori sugli insiemi, che verranno trattati in seguito.
È fondamentale comprendere i valori numerici associati alle parole chiave True
e False
. Queste due parole chiave hanno i valori numerici di 1 e 0, rispettivamente.
True == 1
True
False == 0
True
True + False
1
type(True + False)
int
Oltre ai numeri e ai valori booleani, Python supporta anche un insieme di “contenitori”, ovvero i seguenti tipi strutturati:
Una tupla è una collezione di diversi tipi di dati che è ordinata e immutabile (non modificabile). Le tuple sono scritte tra parentesi tonde, (). Una volta creata una tupla, non è possibile modificarne i contenuti.
= ("Rosso", "Nero", "Bianco")
colors colors
('Rosso', 'Nero', 'Bianco')
type(colors)
tuple
Le stringhe sono tuple di caratteri. Pertanto non sono modificabili.
Gli oggetti di tipo lista sono simili alle tuple, ma con alcune differenze. La lista è un oggetto mutabile, il che significa che possiamo aggiungere o rimuovere elementi dalla lista anche dopo la sua creazione. Una lista viene creata separando i suoi elementi tramite virgola e racchiudendo il tutto tra parentesi quadre.
Si noti che una lista è una struttura dati eterogenea contentente una sequenza di elementi che possono essere di tipo diverso.
= ["Pippo", 3, -2.953, [1, 2, 3]]
my_list my_list
['Pippo', 3, -2.953, [1, 2, 3]]
type(my_list)
list
La lista my_list
è composta da diversi elementi: una stringa (“Pippo”), un numero intero (3), un numero decimale (-2.953) e un’altra lista ([1, 2, 3]).
Gli elementi nella lista sono ordinati in base all’indice, il quale rappresenta la loro posizione all’interno della lista. Gli indici delle liste partono da 0 e aumentano di uno. Per accedere a un elemento della lista tramite il suo indice, si utilizza la notazione delle parentesi quadre: nome_lista[indice]
. Ad esempio:
1] my_list[
3
0] my_list[
'Pippo'
Python prevede alcune funzioni che elaborano liste, come per esempio len
che restituisce il numero di elementi contenuti in una lista:
len(my_list)
4
Benché questa lista contenga come elemento un’altra lista, tale lista nidificata conta comunque come un singolo elemento. La lunghezza di di my_list
è quattro.
Una lista vuota si crea nel modo seguente:
= []
empty_list len(empty_list)
0
Ecco alcuni esempi.
= ["banana", "orange", "mango", "lemon"] # list of fruits
fruits = ["Tomato", "Potato", "Cabbage", "Onion", "Carrot"] # list of vegetables
vegetables
print("Fruits:", fruits)
print("Number of fruits:", len(fruits))
print("Vegetables:", vegetables)
print("Number of vegetables:", len(vegetables))
Fruits: ['banana', 'orange', 'mango', 'lemon']
Number of fruits: 4
Vegetables: ['Tomato', 'Potato', 'Cabbage', 'Onion', 'Carrot']
Number of vegetables: 5
Supponiamo di voler ordinare in ordine alfabetico i nomi presenti nella lista. Per fare ciò, è necessario utilizzare il metodo sort
sulla lista utilizzando la notazione con il punto (dot notation):
= ["Carlo", "Giovanni", "Giacomo"]
names names.sort()
Tale metodo però non restituisce alcun valore, in quanto l’ordinamento è eseguito in place: dopo l’invocazione, gli elementi della lista saranno stati riposizionati nell’ordine richiesto. Visualizziamo la listra trasformata:
names
['Carlo', 'Giacomo', 'Giovanni']
L’invocazione di metodi (e di funzioni) prevede anche la possibilità di specificare degli argomenti opzionali. Per esempio:
=True)
names.sort(reverse names
['Giovanni', 'Giacomo', 'Carlo']
Il metodo remove()
può essere usato per rimuovere elementi da una lista.
print(fruits)
"banana")
fruits.remove(print(fruits)
['banana', 'orange', 'mango', 'lemon']
['orange', 'mango', 'lemon']
Il metodo insert()
può essere usato per aggiungere elementi ad una lista.
print(fruits)
2, "watermelon")
fruits.insert(print(fruits)
['orange', 'mango', 'lemon']
['orange', 'mango', 'watermelon', 'lemon']
È possibile copiare una lista in una nuova variabile:
print(fruits)
= fruits.copy() new_fruits
['orange', 'mango', 'watermelon', 'lemon']
print(new_fruits)
['orange', 'mango', 'watermelon', 'lemon']
L’operatore +
concatena liste:
= [1, 2, 3]
a = [4, 5, 6]
b = a + b
c print(c)
[1, 2, 3, 4, 5, 6]
In maniera simile, l’operatore *
ripete una lista un certo numero di volte:
0] * 4 [
[0, 0, 0, 0]
1, 2, 3] * 3 [
[1, 2, 3, 1, 2, 3, 1, 2, 3]
L’aspetto importante da considerare è che, essendo una sequenza di elementi eterogenei, è difficile eseguire operazioni algebriche sulle liste in Python puro. Ad esempio, consideriamo la seguente lista:
= [1, 2, 3]
x x
[1, 2, 3]
Se desideriamo calcolare una semplice operazione, come la media di x
, è necessario seguire una procedura abbastanza articolata. Ad esempio:
= 0
total = 0
counter
for num in x:
+= 1
counter += num
total
= total / counter
avg
print(avg)
2.0
Indubbiamente, sarebbe preferibile ottenere questo risultato con un approccio più semplice. In seguito, vedremo che se utilizziamo una sequenza di elementi omogenei, il problema può essere risolto in modo molto più agevole. Ad esempio,
import numpy as np
= np.array([1, 2, 3])
x np.mean(x)
2.0
Possiamo contare il numero degli elementi specificati che sono contenuti in una lista usando count()
.
= [22, 19, 24, 25, 26, 24, 25, 24]
ages print(ages.count(24))
3
Possiamo trovare l’indice di un elemento in una lista con index()
.
24) # index of the first occurrence ages.index(
2
L’operatore di slice (:) applicato alle liste in Python consente di estrarre una porzione specifica di elementi dalla lista. L’operatore di slice ha la seguente sintassi: lista[inizio:fine:passo]
.
inizio
rappresenta l’indice di partenza dell’intervallo (inclusivo).fine
rappresenta l’indice di fine dell’intervallo (esclusivo).passo
rappresenta il passo o l’incremento tra gli indici degli elementi selezionati (facoltativo).Ecco alcuni esempi per illustrare l’utilizzo dell’operatore di slice:
= [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
lista
# Estrarre una porzione della lista
= lista[2:6] # [3, 4, 5, 6]
porzione
# Estrarre una porzione con un passo specifico
= lista[1:9:2] # [2, 4, 6, 8]
porzione_passo
# Estrarre una porzione dalla fine della lista
= lista[6:] # [7, 8, 9, 10]
porzione_fine
# Estrarre una porzione dall'inizio della lista
= lista[:5] # [1, 2, 3, 4, 5] porzione_inizio
Gli insiemi sono collezioni finite di elementi distinti e non memorizzati in un ordine specifico. Un insieme non può contenere più di un’istanza dello stesso elemento. Per creare un insieme si utilizzano le parentesi graffe {}. Ad esempio:
= {"A", "B", "C", "D", "E", "F"}
my_set my_set
{'A', 'B', 'C', 'D', 'E', 'F'}
type(my_set)
set
Gli oggetti di tipo “set” sono utili per eseguire operazioni matematiche sugli insiemi.
Per verificare se un elemento esiste in un insieme usiamo l’operatore in
.
print("Does set my_set contain D? ", "D" in my_set)
Does set my_set contain D? True
L’unione di due insieme si ottiene con union()
.
= {"banana", "orange", "mango", "lemon"}
fruits = {"tomato", "potato", "cabbage", "onion", "carrot"}
vegetables print(fruits.union(vegetables))
{'carrot', 'onion', 'lemon', 'mango', 'tomato', 'potato', 'banana', 'cabbage', 'orange'}
L’intersezione di due insieme si trova con intersection()
.
= {"p", "y", "t", "h", "o", "n"}
python = {"d", "r", "a", "g", "o", "n"}
dragon python.intersection(dragon)
{'n', 'o'}
Un insieme può essere un sottoinsieme o un sovrainsieme di altri insiemi.
Per verificare se un insieme è un sottoinsieme di un altro, si utilizza il metodo issubset()
. Per verificare se un insieme è un sovrainsieme di un altro, si utilizza il metodo issuperset()
.
= {"item1", "item2", "item3", "item4"}
st1 = {"item2", "item3"}
st2 st2.issubset(st1)
True
st1.issuperset(st2)
True
La differenza tra due insiemi si ottiene con difference()
.
= {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
whole_numbers = {0, 2, 4, 6, 8, 10}
even_numbers whole_numbers.difference(even_numbers)
{1, 3, 5, 7, 9}
Possiamo verificare se due insiemi sono disgiunti, ovvero non hanno elementi in comune, utilizzando il metodo isdisjoint()
.
= {"item1", "item2", "item3", "item4"}
st1 = {"item2", "item3"}
st2 st2.isdisjoint(st1)
False
Gli oggetti di tipo “dizionario” vengono utilizzati per creare coppie chiave-valore, dove ogni chiave è unica. Un dizionario viene creato specificando ogni coppia come chiave : valore
, separando le diverse coppie con una virgola e racchiudendo il tutto tra parentesi graffe. Ad esempio:
= {
music "blues": "Betty Smith",
"classical": "Gustav Mahler",
"pop": "David Bowie",
"jazz": "John Coltrane",
}
L’accesso agli elementi di un dizionario viene fatto specificando all’interno di parentesi quadre la chiave per ottenere o modificare il valore corrispondente:
"pop"] music[
'David Bowie'
Per trovare il numero di coppie key: value
nel dizionario usiamo len()
.
print(len(music))
4
"new music"] = "Missy Mazzoli"
music[print(music)
{'blues': 'Betty Smith', 'classical': 'Gustav Mahler', 'pop': 'David Bowie', 'jazz': 'John Coltrane', 'new music': 'Missy Mazzoli'}
A volte è utile creare dei contenitori vuoti. I comandi per creare liste vuote, tuple vuote, dizionari vuoti e insiemi vuoti sono rispettivamente lst = []
, tup=()
, dic={}
e st = set()
.
Alla fine di ogni capitolo e, in effetti, alla fine (o all’inizio) di qualsiasi notebook che creiamo, è utile includere informazioni sull’ambiente di calcolo, compresi i numeri di versione di tutti i pacchetti che utilizziamo. Il pacchetto watermark
può essere usato per questo scopo. Il pacchetto watermark
contiene comandi speciali ed è un’estensione di IPython. In generale, per utilizzare tali comandi speciali, li precediamo con il segno % o %% in una cella. Utilizziamo la funzione speciale built-in %load_ext
per caricare watermark
, e quindi utilizziamo %watermark
per invocarlo.
%load_ext watermark
%watermark -n -u -v -iv -w -m
Last updated: Wed Jul 24 2024
Python implementation: CPython
Python version : 3.12.4
IPython version : 8.26.0
Compiler : Clang 16.0.6
OS : Darwin
Release : 23.5.0
Machine : arm64
Processor : arm
CPU cores : 8
Architecture: 64bit
Watermark: 2.4.3
Ecco una spiegazione dettagliata delle opzioni che sono state utilizzate nell’istruzione watermark
.
-n
o --datename
: Aggiunge la data e l’ora correnti al watermark. Questo può essere utile per mantenere una cronologia delle modifiche o delle esecuzioni del notebook.
-u
o --updated
: Mostra l’ultima volta in cui il notebook è stato salvato. È utile per tenere traccia delle modifiche recenti apportate al notebook.
-v
o --python
: Mostra la versione di Python utilizzata nel kernel del notebook. Questo è importante per garantire la compatibilità del codice e replicare gli ambienti di lavoro.
-iv
o --iversions
: Visualizza le versioni delle librerie importate nel notebook. È fondamentale per la replicabilità degli esperimenti e degli analisi, dato che diverse versioni delle librerie possono comportare risultati diversi.
-w
o --watermark
: Aggiunge il watermark stesso, che è semplicemente il logo “watermark”. È più una questione estetica che funzionale.
-m
o --machine
: Fornisce informazioni sulla macchina su cui viene eseguito il Jupyter Notebook, come il tipo di sistema operativo e l’architettura della macchina (ad esempio, x86_64). Questo può essere utile per documentare l’ambiente hardware in cui vengono eseguiti gli esperimenti.
Queste opzioni forniscono un modo semplice e immediato per documentare e tracciare importanti metadati nei notebook Jupyter.