Argomenti principali:
Introduzione Python:
- Cos'è Python e perché è popolare?
- Come installare Python e l'ambiente di sviluppo.
Fondamentali di Python
- Sintassi e semantica di base Python:
- Variabili, tipi di dati e operatori
- Condizioni e controllo del flusso
- Cicli e iterazione
- Funzioni e ricorsione
- Moduli e librerie
Strutture di dati in Python
- Liste, tuple e dizionari
- Set e frozenset
- Stringhe
- Operazioni comuni su strutture di dati
- Comprensione di lista e iterazioni avanzate
Programmare in Python:
- Classi e oggetti
- Ereditarietà e polimorfismo
- Decoratori e meta-programmazione
- Eccezioni e gestione degli errori
Algoritmi e strutture di dati avanzate
- Algoritmi di ricerca e ordinamento
- Alberi e grafi
- Heap e coda prioritaria
- Tabelle hash e algoritmi di hash
- Algoritmi di string matching
Applicazioni pratiche di Python:
- Introduzione alle applicazioni pratiche di Python
- Analisi dei dati con Python e Pandas
- Visualizzazione dei dati con Matplotlib e Seaborn
- Costruzione di interfacce utente con Tkinter
- Costruzione di applicazioni web con Flask o Django
- Progetti di programmazione in Python
Idee per progetti di programmazione in Python
- Struttura di un progetto Python
- Version control e GitHub
- Testing e debugging di un progetto Python
- Conclusioni
- Prospettive future per l'uso di Python
Introduzione Python:
Python è un linguaggio di programmazione ad alto livello orientato agli oggetti e interpretato. Il linguaggio è stato inventato da Guido Van Rossum nel 1991 ed è diventato uno dei linguaggi di programmazione più popolari al mondo.
Questo linguaggio è diventato famoso per diverse caratteristiche che lo distinguevano dai linguaggi del tempo:
- Facilità di apprendimento, infatti ha una sintassi semplice e chiara che lo rende facile da usare anche per i principianti.
- Versatilità, è un linguaggio molto versatile se ci pensate può essere usato per lo sviluppo web, l'analisi dei dati, la creazione di intelligenza artificiale, lo sviluppo di videogiochi, la cybersecurity ecc...
- Scalabilità, dato che si può gestire progetti di grandi dimensioni e crescere in base alle esigenze.
- Open-source, il che significa che il codice sorgente è disponibile pubblicamente e può essere modificato e migliorato da chiunque!
Installazione e ambiente di sviluppo:
Linux:
La maggior parte delle distro Linux hanno installato Python di default. Al massimo aggiornate da terminale.
Per l'ambiente di sviluppo consiglio di utilizzare PyCharm o Visual Studio Code.
In alternativa Vim, Emacs o Nano insieme al terminale.
Windows:
- Scaricare l'installer di Python dal sito ufficiale.
- Avviare il file .exe scaricato e seguire le istruzioni.
- Durante l'installazione selezionare "Aggiungi Python al PATH"
- Dopo l'installazione aprire il prompt dei comandi (cmd) e digitare "python" per controllare se avete installato completamente tutto.
MacOS
- Da terminale digitare "brew install python" per installare Python utilizzando il package manager Homebrew, in alternativa potete scaricare l'installer dal sito ufficiale
- Seguire le istruzioni per l'installazione
- Dopo l'installazione aprire il terminale e digitare "python" per controllare se avete fatto correttamente.
Sintassi di base:
I commenti: I commenti iniziano con il simbolo '#'. Sono utilizzati per aggiungere commenti al codice quindi il testo non viene eseguito:
# Questo è un commento
Assegnazione: Si una il simbolo "=" per assegnare un valore ad una variabile.
variabile = 20
Stampare testo: si usa "print" per stampare in output:
print("Iscriviti a Ethical Hacking Forum!")
Indentazione: utilizzata per definire il blocco di codice, in particolare si dovrebbero usare 4 spazi.
if x > 5:
print("x è maggiore di 5")
Stringhe: Divise da virgolette singole o doppie.
nome = "Pino"
cognome = 'Rossi'
Liste: Cioè una sequenza ordinata di elementi. Importante le quadre!
numeri = [1, 2, 3, 4, 5]
Dizionari: una raccolta di coppie chiave-valore, parentesi graffe!
persona = {'nome': 'Pino', 'cognome': 'Rossi', 'età': 20}
Semantica di base:
Variabili: In Python le variabili sono dinamicamente tipizzate quindi il loro tipo viene determinato automaticamente dal valore che viene loro assegnato.
numero = 5 # un intero
testo = "ciao!" # una stringa
Operazioni aritmetiche: Sono anche disponibili le classiche operazioni aritmetiche come l'addizione, la sottrazione, la moltiplicazione e la divisione.
somma = 5 + 5
sottrazione = 10 - 5
moltiplicazione = 2 * 5
divisione = 20 / 4
Condizioni: In genere si usano gli operatori di confronto (>, <, >=, <=, ==, !=) per creare condizioni.
if x > 5:
print("x è maggiore di 5")
Cicli: Importante differenziare che i cicli for sono utilizzati per iterare su una sequenza, mentre i cicli while vengono usati per eseguire un blocco di codice finché una condizione non viene soddisfatta.
for i in range(5):
print(i)
while x < 10:
x += 1
Costrutti più complessi:
Funzioni: blocchi di codice che possono essere chiamati da altre parti del programma. Sempre input-output:
def somma(a, b):
return a + b
Classi e oggetti: Le classi sono una specie di modello per la creazione di oggetti mentre gli oggetti sono istanze delle classi e contengono attributi e metodi.
class Persona:
def __init__(self, nome, cognome, età):
self.nome = nome
self.cognome = cognome
self.età = età
def saluta(self):
print(f"Ciao, sono {self.nome} {self.cognome}!")
Eccezioni: Le eccezioni sono utilizzate per gestire situazioni anomale durante l'avvio del programma. Molto utile per non interrompere l'esecuzione del programma:
try:
x = 10 / 0
except ZeroDivisionError:
print("Errore: divisione per zero!")
Moduli e librerie: I moduli sono file contenenti codice Python invece i pacchetti sono raccolte di moduli. Sono utilizzati per organizzare e riutilizzare il codice senza tornare a riscrivere tutto.
import math
print(math.pi)
from mio_pacchetto import mia_funzione
mia_funzione()
Decoratori: sono spesso utilizzati per modificare il comportamento di una funzione o di una classe possono anche essere utilizzati per aggiungere funzionalità a una funzione o per modificarne il comportamento in modo dinamico.
def decoratore(funzione):
def nuova_funzione():
print("Inizio funzione")
funzione()
print("Fine funzione")
return nuova_funzione
@decoratore
def mia_funzione():
print("ciao")
Il bello di Python è che ci sono molte librerie e framework disponibili che utilizzano questi costrutti per semplificare la creazione di programmi sofisticati e complessi!
Variabili
Le variabili sono un tipo di dato che permettono di memorizzare informazioni e dati all'interno di un programma. Le variabili possono contenere numeri, stringhe di testo, booleani, liste, tuple, dizionari ecc...
Quindi come si usano? Per usare una variabile in Python bisogna prima assegnarle un nome e un valore. Per esempio per creare una variabile che contenga un numero intero:
numero = 42
Le variabili in Python sono dinamiche questo significa che non bisogna specificare il tipo di dati che saranno memorizzati al momento della creazione della variabile. Per esempio è possibile creare una variabile che contenga una stringa di testo:
nome = "Ethical Hacking Forum"
Esistono diversi tipi di variabili:
- Variabili numeriche: le variabili numeriche possono contenere numeri interi, numeri floating-point o numeri complessi.
x = 5
y = 3.14
z = 2 + 3j
- Variabili di stringa: le variabili di stringa possono contenere una sequenza di caratteri alfanumerici.
nome = "Ethical"
cognome = "Hacking"
saluto = "Salve, come stai?"
- Variabili booleane: classico vero o falso.
vero = True
falso = False
Variabili di lista: le variabili di lista possono contenere una sequenza di valori di qualsiasi tipo di info.
numeri = [1, 2, 3, 4, 5]
frutta = ['mela', 'banana', 'arancia']
mista = [1, 'ciao', True, 3.14]
Variabili di dizionario: che consentono di memorizzare coppie chiave-valore.
dizionario = {'nome': 'Ethical', 'cognome': 'Hacking', 'età': 1}
Argomento molto collegato sono gli operatori che sono simboli o parole chiave speciali che vengono utilizzati per eseguire operazioni su dati e variabili. Per esempio l'operatore "+" viene utilizzato per sommare due numeri, mentre l'operatore "==" viene utilizzato per confrontare due valori.
Riassumendo:
# variabili numeriche
a = 10
b = 5
# operatori aritmetici
somma = a + b
differenza = a - b
prodotto = a * b
quoziente = a / b
modulo = a % b
# risultati
print("Somma:", somma)
print("Differenza:", differenza)
print("Prodotto:", prodotto)
print("Quoziente:", quoziente)
print("Modulo:", modulo)
# variabili di stringa
nome = "Ethical"
cognome = "Hacking"
# operatori di stringa
nome_completo = nome + " " + cognome
saluto = "Ciao " + nome + ", come stai?"
# risultati
print("Nome completo:", nome_completo)
print("Saluto:", saluto)
Condizioni, controllo del flusso e cicli
Le condizioni e il controllo del flusso sono molto importanti perché permettono di gestire il flusso di esecuzione delle istruzioni all'interno di un programma.
Le condizioni sono espressioni che restituiscono un valore (True o False) e sono utilizzate per decidere se eseguire o meno un blocco di codice. Le condizioni sono definite utilizzando gli operatori logici (and, or, not) e i confronti (>, <, >=, <=, ==, !=).
I più comuni in Python sono:
- "if": permette di eseguire un blocco di codice solo se la condizione specificata è vera.
x = 5
if x > 0:
print("x è positivo")
- "if-else": permette di eseguire un blocco di codice se la condizione specificata è vera e un altro blocco di codice se la condizione è falsa.
x = 5
if x > 0:
print("x è positivo")
else:
print("x è negativo o nullo")
- "if-elif-else": permette di specificare più condizioni da valutare in sequenza e di eseguire il blocco di codice corrispondente alla prima condizione vera. Se nessuna delle condizioni è vera viene eseguito il blocco di codice dell'istruzione "else".
x = 5
if x < 0:
print("x è negativo")
elif x == 0:
print("x è nullo")
else:
print("x è positivo")
- Il ciclo "for": con questo si può iterare su una sequenza di valori (come una lista o una stringa) e eseguire un blocco di codice per ogni valore nella sequenza.
frutta = ["mela", "banana", "arancia"]
for f in frutta:
print(f)
- Il ciclo "while": permette di eseguire un blocco di codice finché una condizione specificata è vera.
x = 0
while x < 5:
print(x)
x += 1
Cicli, esempio più complesso
Vediamo ora un caso dove si utilizza un ciclo "while" per eseguire un blocco di codice tre volte. All'interno del ciclo "while" il codice utilizza un ciclo "for" per iterare su ogni elemento della lista "frutta" per poi mostrare i risultati:
frutta = ["mela", "banana", "kiwi"]
x = 1
while x <= 3:
print("Iterazione " + str(x) + ":")
for frutto in frutta:
print(frutto)
x += 1
Output:
Iterazione 1:
mela
banana
kiwi
Iterazione 2:
mela
banana
kiwi
Iterazione 3:
mela
banana
kiwi
Funzioni e ricorsione
Le funzioni sono delle parti di codice che eseguono un'operazione specifica e possono essere richiamate all'interno di un programma. La sintassi di una funzione è questa:
def nome_funzione(argomento_1, argomento_2, ...):
# codice da eseguire
return risultato
- def indica che si sta definendo una funzione.
- nome_funzione è il nome della funzione.
- Tra parentesi tonde vengono elencati gli argomenti che la funzione accetta. Gli argomenti possono essere opzionali.
- Il corpo della funzione viene definito con il codice.
- La parola return restituisce un valore dalla funzione.
Ecco un esempio di funzione che prende in input due numeri e restituisce la loro somma:
def somma(a, b):
risultato = a + b
return risultato
La funzione può essere chiamata passando i valori dei suoi argomenti:
risultato = somma(2, 3)
print(risultato) # Output: 5
La ricorsione è un concetto in cui una funzione si richiama a se stessa per risolvere un problema. La ricorsione può essere utilizzata per risolvere problemi che possono essere divisi in sottoproblemi simili.
Ad esempio una funzione ricorsiva che calcola il fattoriale di un numero:
def fattoriale(n):
if n == 0:
return 1
else:
return n * fattoriale(n-1)
La funzione controlla se il numero passato come argomento è uguale a 0. Se è proprio così restituisce 1 sennò la funzione si richiama passando il numero decrementato di 1 e moltiplicandolo per il risultato della chiamata ricorsiva. Vi lascio un esempio:
risultato = fattoriale(5)
print(risultato) # Output: 120
Ora la funzione calcola il fattoriale di 5 moltiplicando 5 per il fattoriale di 4 poi moltiplicando il risultato per 3, poi per 2, e concludendo per 1.
Moduli e librerie
I moduli e le librerie sono strumenti utilissimi utilizzati per estendere le funzionalità di base del linguaggio fornendo moltissime di funzioni e classi che possono essere importate e utilizzate nei programmi evitando sempre di riscrivere tutto.
Un modulo è un file che contiene funzioni, variabili e classi. In pratica è un modo per organizzare il codice in modo da poterlo riutilizzare in più parti. Per esempio "math" che offfre funzioni matematiche comuni come sqrt (radice quadrata), sin (seno), cos (coseno), ecc.
Una libreria invece è un insieme di moduli che possono essere utilizzati insieme per risolvere un problema o svolgere un'attività. Per esempio "NumPy" ha funzionalità per la manipolazione di array multidimensionali, invece "Pandas" viene usato per la manipolazione di dati strutturati come fogli di calcolo.
Per usare un modulo o una libreria bisogna prima importarla, vi faccio un semplice esempio:
import math
print(math.sqrt(25))
Si può anche importare solo alcune funzioni o classi specifiche da un modulo o una libreria utilizzando la sintassi "from nome_modulo import nome_funzione".
from math import sqrt
print(sqrt(25))
Liste, tuple e dizionari
Le liste, le tuple e i dizionari sono strutture di dati comuni usate per organizzare e manipolare valori.
Una lista è una serie ordinata di elementi che possono essere di qualsiasi tipo (int, float, stringa, ecc.). Le liste sono definite tra parentesi quadre [] e gli elementi sono separati da virgole.
my_list = [1, "due", 3.0, "quattro"]
Le tuple sono simili alle liste ma sono immutabili, significa che una volta definite non possono essere modificate. Le tuple sono definite tra parentesi tonde () e gli elementi sono separati da virgole.
my_tuple = (1, "due", 3.0, "quattro")
I dizionari sono una serie di elementi che consistono in una coppia chiave-valore. Le chiavi sono utilizzate per accedere ai valori associati. I dizionari sono definiti tra parentesi graffe {} e le coppie chiave-valore sono separate da virgole.
my_dict = {"nome": "Mario", "cognome": "Rossi", "età": 30}
Solitamente le liste sono usate per gestire collezioni di elementi ordinati, le tuple sono usate per definire un insieme di valori immutabili e i dizionari sono usati per memorizzare e accedere ad informazioni con una chiave di ricerca.
Set e frozenset
I set e frozenset sono strutture dati usate per contenere una serie di elementi unici cioè elementi che non possono essere duplicati all'interno della serie.
Un set è un tipo di dati mutable, quindi si può modificarlo aggiungendo o rimuovendo elementi, mentre un frozenset è un tipo di dati immutabile ciò significa che non è possibile modificarlo una volta che è stato creato.
La sintassi del set è questa:
mio_set = {elemento1, elemento2, elemento3}
Si può anche creare un set vuoto e aggiungere gli elementi successivamente utilizzando add() o update():
mio_set = set()
mio_set.add(elemento1)
mio_set.add(elemento2)
La sintassi del frozenset è questa:
mio_frozenset = frozenset([elemento1, elemento2, elemento3])
Entrambi supportano operazioni come l'unione (union), l'intersezione (intersection) e la differenza (difference) tra i set o frozenset, oltre a tutte le altre ovviamente.
Supponiamo di avere due set:
set1 = {1, 2, 3}
set2 = {3, 4, 5}
Possiamo unire i due set utilizzando l'operatore di unione "|":
set3 = set1 | set2
In questo caso il risultato è {1, 2, 3, 4, 5}
Stringhe:
Una stringa è una sequenza immutabile di caratteri. Le stringhe iniziano e terminano con un singolo apice ('') o un doppio apice (""). Esempio "ciao" è una stringa valida.
Puoi anche utilizzare il carattere di escape () per inserire caratteri speciali all'interno di una stringa. Per esempio se vuoi inserire una virgoletta all'interno di una stringa racchiusa da virgolette:
stringa_con_virgoletta = "Questa stringa contiene una \"virgoletta\""
Per accedere ad un singolo carattere all'interno di una stringa puoi usare l'indicizzazione (indice) e indicare la posizione del carattere all'interno della stringa. L'indice del primo carattere della stringa è 0.
nome = "Ethical Hacking Forum"
print(nome[0]) # Output: E
Puoi anche utilizzare slicing (fetta) per estrarre una parte della stringa. Slicing usa due indici separati da due punti: il primo indice indica il carattere di partenza invece il secondo indice indica il carattere di fine (escluso).
messaggio = "Ciao a tutti"
print(messaggio[0:4]) # Output: Ciao
Operazioni comuni su strutture di dati
Iniziamo con le liste:
- Aggiungere un elemento alla fine della lista: lista.append(elemento)
- Inserire un elemento in una posizione specifica: lista.insert(posizione, elemento)
- Rimuovere l'ultimo elemento della lista: lista.pop()
- Rimuovere un elemento in una posizione specifica: lista.pop(posizione)
- Ottenere la lunghezza della lista: len(lista)
- Verificare se un elemento è presente nella lista: elemento in lista
- Ottenere l'indice di un elemento nella lista: lista.index(elemento)
- Ordinare la lista: lista.sort()
- Invertire l'ordine degli elementi nella lista: lista.reverse()
Le tuple:
- Accedere a un elemento: tupla[indice]
- Verificare se un elemento è presente nella tupla: elemento in tupla
- Ottenere il numero di occorrenze di un elemento nella tupla: tupla.count(elemento)
- Concatenare due tuple: tupla1 + tupla2
Set:
- Aggiungere un elemento al set: set.add(elemento)
- Rimuovere un elemento dal set: set.remove(elemento)
- Verificare se un elemento è presente nel set: elemento in set
- Ottenere la lunghezza del set: len(set)
- Unire due set: set1.union(set2)
- Intersezione di due set: set1.intersection(set2)
- Differenza di due set: set1.difference(set2)
Dizionari:
- Aggiungere una nuova coppia chiave-valore: dizionario[chiave] = valore
- Ottenere il valore corrispondente a una chiave: dizionario[chiave]
- Verificare se una chiave è presente nel dizionario: chiave in dizionario
- Rimuovere una coppia chiave-valore dal dizionario: del dizionario[chiave]
- Ottenere una lista delle chiavi del dizionario: dizionario.keys()
- Ottenere una lista dei valori del dizionario: dizionario.values()
- Ottenere una lista di tuple contenenti coppie chiave-valore: dizionario.items()
Comprensione di lista e iterazioni avanzate
Una lista è una struttura dati che può contenere più valori di diverso tipo ad esempio numeri, stringhe e altre liste.
Le iterazioni sono un meccanismo che ci permette di eseguire un blocco di codice ripetutamente fino a quando una condizione viene soddisfatta. Per esempio si possono utilizzare i cicli "for" e "while" per iterare attraverso gli elementi di una lista.
Ad esempio ora il ciclo "for" attraversa la lista "fruits" e restituisce ogni elemento della lista.
fruits = ["mela", "banana", "kiwi", "arancia"]
for fruit in fruits:
print(fruit)
Python offre anche la possibilità di utilizzare la funzione "enumerate" per iterare attraverso una lista e tenere traccia degli indici degli elementi:
fruits = ["mela", "banana", "kiwi", "arancia"]
for index, fruit in enumerate(fruits):
print(index, fruit)
Si può utilizzare la funzione "zip" per iterare attraverso più liste contemporaneamente:
fruits = ["mela", "banana", "kiwi", "arancia"]
colors = ["rosso", "giallo", "verde", "arancione"]
for fruit, color in zip(fruits, colors):
print(fruit, color)
Si può utilizzare la funzione "filter" per creare una nuova lista che contiene solo gli elementi che soddisfano una determinata condizione.
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even_numbers = list(filter(lambda x: x % 2 == 0, numbers))
print(even_numbers)
In questo caso la funzione "filter" filtra la lista "numbers" e crea una nuova lista "even_numbers" che contiene solo i numeri pari. La funzione "lambda" è la condizione di filtraggio.
Programmare in Python
Classi e oggetti
Iniziamo con la "vera" programmazione in Python introducendo le classi e oggetti.
In riassunto una classe è un tipo di oggetto che rappresenta una struttura di dati che definisce un insieme di attributi e metodi che possono essere utilizzati per creare istanze di oggetti. Un oggetto è un'istanza di una classe che ha accesso agli attributi e ai metodi definiti dalla classe.
Cose molto importanti da ricordare:
Definizione di una classe: per definire una classe bisogna utilizzare la parola chiave class seguita dal nome della classe e da due punti. All'interno della classe è possibile definire attributi e metodi.
Attributi: gli attributi sono variabili definite all'interno di una classe che rappresentano le proprietà dell'oggetto. Gli attributi possono essere interi, float, stringhe, liste, tuple ecc...
Metodi: i metodi sono funzioni definite all'interno di una classe che operano su un oggetto della classe. I metodi possono accedere e modificare gli attributi dell'oggetto e possono anche restituire un valore.
Creazione di un oggetto: per creare un oggetto di una classe in Python bisogna utilizzare il nome della classe dopo le parentesi tonde.
Utilizzo di un oggetto: una volta creato un oggetto si può accedere ai suoi attributi e metodi utilizzando la sintassi "nomeoggetto.nomeattributo" o "nomeoggetto.nomemetodo()".
Ereditarietà: si può creare una nuova classe basata su una classe esistente. Questo viene fatto utilizzando il concetto di ereditarietà in cui la nuova classe eredita gli attributi e i metodi della classe esistente.
Polimorfismo: il polimorfismo consente a diversi oggetti di rispondere allo stesso metodo in modo diverso. Ciò consente di scrivere codice più flessibile e riutilizzabile anche per un discorso di eleganza e ottimizzazione.
Ereditarietà e polimorfismo
L'ereditarietà e il polimorfismo sono concetti fondamentali della programmazione orientata agli oggetti (OOP).
L'ereditarietà in Python consente ad una classe di ereditare tutti i metodi e gli attributi di una classe padre. La sintassi per la definizione di una classe derivata (o sottoclasse) è questa:
class ClasseFiglia(ClassePadre):
def __init__(self, arg1, arg2, ...):
super().__init__(arg1, arg2, ...)
# comandi classe figlia
Quindi ClasseFiglia è la classe derivata e ClassePadre è la classe padre. La classe figlia eredita tutti i metodi e gli attributi della classe padre. self viene passato ai metodi sia della classe padre che della classe figlia.
Il polimorfismo in Python consente a diverse classi di implementare gli stessi metodi in modi diversi. Per esempio si può definire un metodo area() in una classe Figura che viene poi implementato in classi derivate come Rettangolo e Cerchio in modo differente. Così l'applicazione che usa questi oggetti può chiamare il metodo area() su oggetti di diverse classi e ottenere risultati corretti.
class Figura:
def area(self):
pass
class Rettangolo(Figura):
def __init__(self, base, altezza):
self.base = base
self.altezza = altezza
def area(self):
return self.base * self.altezza
class Cerchio(Figura):
def __init__(self, raggio):
self.raggio = raggio
def area(self):
return 3.14 * self.raggio ** 2
Decoratori e meta-programmazione
In Python i decoratori sono una funzionalità che consente di modificare il comportamento di una funzione o di una classe senza dover modificare il codice sorgente della funzione o della classe stessa. I decoratori sono essenzialmente funzioni che prendono come argomento un'altra funzione o classe e restituiscono una nuova funzione o classe che ha modificato il comportamento dell'originale.
Invece la meta-programmazione è la capacità (del programma) di modificare se stesso o di generare del codice durante l'esecuzione. La meta-programmazione può essere realizzata utilizzando i decoratori dato che i decoratori consentono di modificare il comportamento di una funzione o di una classe durante l'esecuzione del programma.
Ecco un banale esempio:
def my_decorator(func):
def wrapper(*args, **kwargs):
print("Before function is called")
result = func(*args, **kwargs)
print("After function is called")
return result
return wrapper
@my_decorator
def my_function():
print("Inside function")
my_function()
Dove definiamo il decoratore my_decorator che prende come argomento una funzione func. Il decoratore restituisce una nuova funzione wrapper che avvolge l'originale func. La funzione wrapper viene quindi restituita al punto in cui viene chiamata my_decorator.
Abbiamo quindi utilizzato il decoratore my_decorator per modificare il comportamento della funzione my_function durante l'esecuzione del programma.
Eccezioni e gestione degli errori:
Le eccezioni sono degli oggetti che vengono richiamati quando si verifica un errore durante l'esecuzione del programma. Un'eccezione può essere sollevata quando si cerca di dividere per zero, si cerca di accedere a un indice che non esiste in una lista o quando si cerca di aprire un file che non esiste.
Questa gestione permette di gestire queste eccezioni in modo appropriato per evitare che il programma si interrompa in modo anomalo o che l'utente riceva un messaggio di errore senza senso.
Solitamente si basa sull'uso delle istruzioni try e except. L'istruzione try viene utilizzata per eseguire del codice che potrebbe richiamare un'eccezione invece except viene utilizzata per gestire l'eccezione richiamata.
Vediamo un esempio dove si utilizza l'istruzione try per dividere due numeri e l'istruzione except per gestire l'eccezione ZeroDivisionError che viene sollevata se il secondo numero è uguale a zero:
try:
risultato = 10 / 0
except ZeroDivisionError:
print("Errore: divisione per zero")
Vediamo un altro esempio dove gestiamo più di una situazione:
try:
f = open("file_non_esistente.txt")
x = 10 / 0
except FileNotFoundError:
print("Errore: il file non esiste")
except ZeroDivisionError:
print("Errore: divisione per zero")
Si può anche utilizzare l'istruzione else in un blocco try-except per eseguire del codice se nessuna eccezione viene sollevata:
try:
x = 10 / 2
except ZeroDivisionError:
print("Errore: divisione per zero")
else:
print("Il risultato è:", x)
Algoritmi e strutture di dati avanzate
Algoritmi di ricerca e ordinamento:
Solitamente in Python gli algoritmi di ricerca e ordinamento sono utilizzati per manipolare le liste o gli array.
Vediamo ora gli algoritmi più conoscuti:
Algoritmi di ricerca:
- Ricerca lineare (Linear Search)
- Ricerca binaria (Binary Search)
Algoritmi di ordinamento:
- Bubble Sort
- Selection Sort
- Insertion Sort
- Merge Sort
- Quick Sort
- Heap Sort
- Counting Sort
- Radix Sort
Vediamo ad esempio quello di Ricerca lineare:
def linear_search(arr, x):
for i in range(len(arr)):
if arr[i] == x:
return i
return -1 # se l'elemento non è presente
arr = [5, 2, 9, 7, 3]
x = 7
result = linear_search(arr, x)
print(result) # Output: 3
Ricerca binaria:
def binary_search(arr, x):
low = 0
high = len(arr) - 1
while low <= high:
mid = (low + high) // 2
if arr[mid] == x:
return mid
elif arr[mid] < x:
low = mid + 1
else:
high = mid - 1
return -1 # se l'elemento non è presente
arr = [2, 3, 5, 7, 9]
x = 7
result = binary_search(arr, x)
print(result) # Output: 3
Bubble Sort:
def bubble_sort(arr):
n = len(arr)
for i in range(n):
for j in range(0, n-i-1):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
arr = [5, 2, 9, 7, 3]
bubble_sort(arr)
print(arr) # Output: [2, 3, 5, 7, 9]
Selection Sort:
def selection_sort(arr):
n = len(arr)
for i in range(n):
min_idx = i
for j in range(i+1, n):
if arr[j] < arr[min_idx]:
min_idx = j
arr[i], arr[min_idx] = arr[min_idx], arr[i]
arr = [5, 2, 9, 7, 3]
selection_sort(arr)
print(arr) # Output: [2, 3, 5, 7, 9]
Insertion Sort:
def insertion_sort(arr):
n = len(arr)
for i in range(1, n):
key = arr[i]
j = i - 1
while j >= 0 and key < arr[j]:
arr[j+1] = arr[j]
j -= 1
arr[j+1] = key
arr = [5, 2, 9, 7, 3]
insertion_sort(arr)
print(arr) # Output: [2, 3, 5, 7, 9]
Merge Sort:
def merge_sort(arr):
if len(arr) > 1:
mid = len(arr) // 2
left_half = arr[:mid]
right_half = arr[mid:]
merge_sort(left_half)
merge_sort(right_half)
i = j = k = 0
while i < len(left_half) and j < len(right_half):
if left_half[i] < right_half[j]:
arr[k] = left_half[i]
i += 1
else:
arr[k] = right_half[j]
j += 1
k += 1
while i < len(left_half):
arr[k] = left_half[i]
i += 1
k += 1
while j < len(right_half):
arr[k] = right_half[j]
j += 1
k += 1
arr = [5, 2, 9, 7, 3]
merge_sort(arr)
print(arr) # Output: [2, 3, 5, 7, 9]
Quick Sort:
def quick_sort(arr, low, high):
if low < high:
pivot = partition(arr, low, high)
quick_sort(arr, low, pivot-1)
quick_sort(arr, pivot+1, high)
def partition(arr, low, high):
pivot = arr[high]
i = low - 1
for j in range(low, high):
if arr[j] <= pivot:
i += 1
arr[i], arr[j] = arr[j], arr[i]
arr[i+1], arr[high] = arr[high], arr[i+1]
return i+1
arr = [5, 2, 9, 7, 3]
quick_sort(arr, 0, len(arr)-1)
print(arr) # Output: [2, 3, 5, 7, 9]
Heap Sort:
def heap_sort(arr):
n = len(arr)
for i in range(n // 2 - 1, -1, -1):
heapify(arr, n, i)
for i in range(n-1, 0, -1):
arr[i], arr[0] = arr[0], arr[i]
heapify(arr, i, 0)
def heapify(arr, n, i):
largest = i
l = 2 * i + 1
r = 2 * i + 2
if l < n and arr[l] > arr[largest]:
largest = l
if r < n and arr[r] > arr[largest]:
largest = r
if largest != i:
arr[i], arr[largest] = arr[largest], arr[i]
heapify(arr, n, largest)
arr = [5, 2, 9, 7, 3]
heap_sort(arr)
print(arr) # Output: [2, 3, 5, 7, 9]
Counting Sort:
def counting_sort(arr):
max_element = max(arr)
min_element = min(arr)
range_of_elements = max_element - min_element + 1
count_arr = [0] * range_of_elements
output_arr = [0] * len(arr)
for i in range(0, len(arr)):
count_arr[arr[i]-min_element] += 1
for i in range(1, len(count_arr)):
count_arr[i] += count_arr[i-1]
for i in range(len(arr)-1, -1, -1):
output_arr[count_arr[arr[i]-min_element] - 1] = arr[i]
count_arr[arr[i]-min_element] -= 1
for i in range(0, len(arr)):
arr[i] = output_arr[i]
arr = [5, 2, 9, 7, 3]
counting_sort(arr)
print(arr) # Output: [2, 3, 5, 7, 9]
Alberi e grafi:
Gli alberi e i grafi sono implementati come strutture dati utilizzando classi, liste e dizionari.
Un albero è una struttura dati composta da nodi interconnessi in cui ogni nodo ha un padre (ad eccezione del nodo radice che non ha un padre) e zero o più figli. Un albero può essere rappresentato utilizzando una classe Nodo che possiede un valore e una lista collegata ai nodi figli.
Albero:
class Nodo:
def __init__(self, valore):
self.valore = valore
self.figli = []
def aggiungi_figlio(self, figlio):
self.figli.append(figlio)
Grafo:
grafo = {
'A': ['B', 'C'],
'B': ['C', 'D'],
'C': ['D'],
'D': ['C'],
'E': ['F'],
'F': ['C']
}
Esistono anche librerie Python specializzate per la gestione di alberi e grafi come ad esempio NetworkX e igraph. Queste librerie offrono molte funzioni utili per la creazione, la manipolazione e l'analisi di alberi e grafi.
Heap e coda prioritaria:
Diciamo subito che sono due strutture dati utilizzate per gestire una collezione di elementi in cui ogni elemento ha un valore di priorità. Queste strutture dati sono implementate attraverso le classi heapq e queue.PriorityQueue.
Un heap è una struttura dati che mantiene un insieme di elementi ordinati in base al valore di priorità. In un heap, l'elemento con la priorità più alta si trova sempre in cima (in posizione 0) e può essere rimosso (usando la funzione heappop() della classe heapq). Gli heap sono implementati come liste e sono gestiti dalla classe heapq. Per aggiungere un elemento a un heap si utilizza la funzione heappush() della classe heapq.
import heapq
# creazione di un heap vuoto
heap = []
# aggiunta di elementi all'heap
heapq.heappush(heap, 3)
heapq.heappush(heap, 1)
heapq.heappush(heap, 4)
# rimozione dell'elemento con la priorità più alta
elemento_prioritario = heapq.heappop(heap)
print(elemento_prioritario) # output: 1
Una coda prioritaria è una struttura dati in cui gli elementi vengono elaborati in ordine di priorità ad esempio l'elemento con la priorità più alta che viene elaborato per primo. Le code prioritarie sono implementate attraverso la classe queue.PriorityQueue.
import queue
# creazione di una coda prioritaria vuota
q = queue.PriorityQueue()
# aggiunta di elementi alla coda prioritaria
q.put((2, "elemento 1"))
q.put((1, "elemento 2"))
q.put((3, "elemento 3"))
# rimozione dell'elemento con la priorità più alta
elemento_prioritario = q.get()
print(elemento_prioritario) # output: (1, "elemento 2")
Tabelle hash e algoritmi di hash:
Una tabella hash è una struttura dati che permette di memorizzare e accedere a valori associati a chiavi in modo semplice ed efficiente.
Un algoritmo di hash è un procedimento che trasforma un input (ad esempio una stringa o un file) in un valore di lunghezza fissa in pratica l'hash.
Esistono molti esempi validi di tabelle hash e algoritmi di hash. Una delle più comuni di tabella hash è la classe dict.
Ad esempio vediamo quando le chiavi sono le stringhe 'rosso', 'verde' e 'blu' mentre i valori sono tuple di tre numeri interi che rappresentano colori RGB.
colori = {'rosso': (255, 0, 0), 'verde': (0, 255, 0), 'blu': (0, 0, 255)}
Per quanto riguarda gli algoritmi di hash vediamo un esempio dove la stringa viene scritta in UTF-8 (perché l'algoritmo di hash richiede un input di tipo bytes) e poi passata alla funzione sha256 di hashlib che restituisce hashlib.sha256. L'hash viene infine ottenuto tramite hexdigest che restituisce una stringa di 64 caratteri esadecimali che rappresenta l'hash.
import hashlib
stringa = 'Hello, world!'
hash = hashlib.sha256(stringa.encode('utf-8')).hexdigest()
print(hash)
Algoritmi di string matching:
Gli algoritmi di string matching sono utilizzati per cercare una sottostringa all'interno di una stringa più grande. Tra il più comune è l'algoritmo di Rabin-Karp e l'algoritmo di Knuth-Morris-Pratt (KMP).
L'algoritmo di Rabin-Karp utilizza una funzione hash per trovare un'uguaglianza tra la sottostringa e la stringa principale. Utilizza una finestra per calcolare la funzione hash della sottostringa e confrontarla con la funzione hash della finestra della stringa principale. Se le funzioni hash corrispondono l'algoritmo confronta i caratteri all'interno della finestra. Se i caratteri corrispondono l'algoritmo restituisce l'indice di inizio della sottostringa nella stringa principale. In caso contrario viene spostata avanti.
def rabin_karp(string, pattern):
prime = 101
pattern_hash = 0
string_hash = 0
num_matches = 0
# funzione hash
for i in range(len(pattern)):
pattern_hash += ord(pattern[i]) * (prime ** i)
# funzione hash finestra scorrevole
for i in range(len(pattern)):
string_hash += ord(string[i]) * (prime ** i)
# cerca la sottostringa nella stringa principale
for i in range(len(string) - len(pattern) + 1):
if pattern_hash == string_hash:
# confronta i caratteri all'interno della finestra
matched = True
for j in range(len(pattern)):
if pattern[j] != string[i+j]:
matched = False
break
if matched:
num_matches += 1
# funzione hash finestra successiva
if i < len(string) - len(pattern):
string_hash = (string_hash - ord(string[i])) / prime
string_hash += ord(string[i+len(pattern)]) * (prime ** (len(pattern)-1))
return num_matches
L'algoritmo di Knuth-Morris-Pratt (KMP) usa una tabella di pre-elaborazione per evitare di confrontare i caratteri già confrontati in precedenza durante la ricerca della sottostringa. La tabella di pre-elaborazione viene verificata con una scansione della sottostringa per identificare il più lungo prefisso. Questo prefisso-suffisso viene utilizzato per calcolare gli spostamenti necessari durante la scansione della stringa principale.
def kmp(string, pattern):
# tabella di pre-elaborazione
table = [0] * len(pattern)
j = 0
for i in range(1, len(pattern)):
while j > 0 and pattern[j] != pattern[i]:
j = table[j-1]
if pattern[j] == pattern[i]:
j += 1
table[i] = j
# cerca sottostringa nella stringa principale
num_matches = 0
j = 0
for i in range(len(string)):
while j > 0 and pattern[j] != string[i]:
j = table[j-1]
if pattern[j] == string[i]:
j += 1
if j == len(pattern):
num_matches += 1
j = table[j-1]
return num_matches
Applicazioni pratiche di Python:
Introduzione alle applicazioni pratiche di Python:
Come abbiamo detto all'inizio Python è un linguaggio di programmazione molto popolare grazie alla sua semplicità, flessibilità e vasta gamma di librerie e framework disponibili.
Gli utilizzi sono davvero infiniti però possiamo riassumerli in diverse categorie come:
- Web development: Python viene utilizzato per sviluppare applicazioni web e API grazie ai suoi framework come Django, Flask e Pyramid. Vediamo per esempio una discussione interessante di @Giulio_M Pyscript: Python per HTML e per il web.
Data analysis e machine learning: Python è uno dei linguaggi preferiti per la data analysis e il machine learning grazie alle sue librerie come NumPy, Pandas, Matplotlib e Scikit-learn.
Automazione dei processi: per automatizzare compiti ripetitivi e noiosi come l'elaborazione di file, l'estrazione di dati dai siti web, la gestione delle email ecc... anche se in termini di performance pecca.
- Giochi e applicazioni grafiche: per sviluppare giochi e applicazioni grafiche grazie alle sue librerie come Pygame, PyOpenGL e Tkinter, vedi ad esempio scacchi in Python.
- Networking: per lo sviluppo di applicazioni di rete e protocolli di comunicazione grazie alle sue librerie come Socket e Requests.
- Intelligenze artificiali: ora vanno anche tanto di moda, è uno dei linguaggi più utilizzato in questo ambito anche per le sue liberie librerie utili per l'AI, come TensorFlow, Keras, PyTorch e SciPy.
Analisi dei dati con Python e Pandas
Pandas è una libreria Python utilizzata per la manipolazione e l'analisi dei dati. Essa fornisce strutture dati flessibili per la manipolazione di dati strutturati come i dati tabulari.
Vediamo alcuni esempi:
Importare le librerie necessarie:
import pandas as pd
import numpy as np
Caricare i dati:
df = pd.read_csv('nomefile.csv')
Verificare la struttura dei dati:
df.head() # Visualizza le prime 5 righe del dataframe
df.info() # Informazioni sul dataframe, come il numero di righe e colonne e il tipo di dato per ogni colonna
df.describe() # Statistiche descrittive per tutte le colonne numeriche
Manipolare i dati:
df.dropna() # Rimuove le righe che contengono valori mancanti
df.drop_duplicates() # Rimuove le righe duplicate
df.groupby('colonna').mean() # Calcola la media per gruppi in base alla colonna specificata
df['nuova_colonna'] = df['colonna1'] + df['colonna2'] # Crea una nuova colonna
Visualizzare i dati:
df.plot(kind='line', x='colonna1', y='colonna2') # Grafico a linea
df.plot(kind='bar', x='colonna1', y='colonna2') # Grafico a barre
df.plot(kind='scatter', x='colonna1', y='colonna2') # Grafico a dispersione
Visualizzazione dei dati con Matplotlib e Seaborn:
Matplotlib e Seaborn sono librerie per la visualizzazione di dati. Matplotlib è una libreria di visualizzazione dei dati di base e Seaborn è una libreria più avanzata che si basa su Matplotlib e fornisce una sintassi più semplice per creare grafici più belli e complessi.
Ecco un esempio di come utilizzare Matplotlib e Seaborn per creare un grafico a barre:
import matplotlib.pyplot as plt
import seaborn as sns
import numpy as np
# Dati di esempio
valori = np.array([10, 20, 30, 40, 50])
etichette = np.array(['A', 'B', 'C', 'D', 'E'])
# Creazione del grafico a barre
fig, ax = plt.subplots()
sns.barplot(x=etichette, y=valori, ax=ax)
# Personalizzazione del grafico
ax.set_title('Grafico a barre')
ax.set_xlabel('Etichette')
ax.set_ylabel('Valori')
# Visualizzazione del grafico
plt.show()
Abbiamo utilizzato la funzione sns.barplot() di Seaborn per creare il grafico a barre e abbiamo personalizzato il grafico utilizzando le funzioni ax.set_title(), ax.set_xlabel() e ax.set_ylabel() di Matplotlib.
Vediamo delle discussioni interessanti che ha fatto @Giulio_M:
Costruzione di interfacce utente con Tkinter:
Tkinter è una libreria standard di Python per la creazione di interfacce grafiche che consente di creare finestre, pulsanti, caselle di testo e molti altri elementi grafici.
import tkinter as tk
root = tk.Tk()
root.mainloop()
Questo codice crea una finestra vuota e la mantiene aperta finché l'utente non la chiude.
Per creare un pulsante nella finestra si può creare un'istanza della classe Button e assegnarla alla finestra:
button = tk.Button(root, text="Clicca qui")
button.pack()
Tkinter consente di gestire gli eventi come i clic sui pulsanti utilizzando la funzione bind. Ad esempio per gestire il clic sul pulsante si può definire una funzione:
def on_button_click():
print("Hai cliccato il pulsante!")
button.bind("<Button-1>", on_button_click)
Idee per progetti di programmazione in Python
Idee per progetti:
- Web scraping: Utilizza le librerie BeautifulSoup e Requests per estrarre informazioni da siti web e analizzarle.
- Automazione: Crea script per automatizzare attività quotidiane come organizzare file, inviare e-mail o aggiornare fogli di calcolo.
- Sviluppo di applicazioni web: Usa Flask o Django per sviluppare applicazioni web.
- Elaborazione dati: Analizza e manipola grandi set di dati utilizzando pandas, NumPy e matplotlib.
- Machine learning e AI: Implementa algoritmi di apprendimento automatico con scikit-learn o TensorFlow.
- Creazione di bot: Sviluppa bot per piattaforme come Discord o Telegram.
- Sviluppo di videogiochi: Usa Pygame per creare giochi 2D.
Struttura di un progetto:
Una struttura di progetto ben organizzata semplifica lo sviluppo, il test e la manutenzione.
Ecco un esempio:
progetto/
│
├── progetto/
│ ├── __init__.py
│ ├── modulo1.py
│ └── modulo2.py
│
├── tests/
│ ├── __init__.py
│ ├── test_modulo1.py
│ └── test_modulo2.py
│
├── .gitignore
├── README.md
├── requirements.txt
└── setup.py
Version control e GitHub
Il controllo delle versioni è fondamentale per tracciare le modifiche al codice e collaborare con altri sviluppatori anche per legare una community. Git è un sistema di controllo delle versioni distribuito ampiamente utilizzato. GitHub è una piattaforma basata su Git che facilita la condivisione e la collaborazione.
- Installa Git sul tuo computer e crea un account GitHub.
- Inizializza un repository Git nel tuo progetto con git init.
- Aggiungi file al repository con git add e crea un commit con git commit.
- Connetti il tuo repository locale a un repository remoto su GitHub.
- Sincronizza i cambiamenti tra il repository locale e il remoto con git push e git pull.
Secondo me Python continuerà a essere un linguaggio di programmazione popolare e in crescita. Con l'espansione dei settori dell'intelligenza artificiale, del machine learning e della scienza dei dati, la domanda di sviluppatori Python rimarrà alta. Voi cosa ne pensate??
Autori:
@Samueleex
@Giulio_M