Introduzione a Python

Guida introduttiva a Python: scopri la differenza tra compilazione e interpretazione, come usare pip e venv, i tipi di dato fondamentali e la sintassi delle funzioni.

Immagine generata con AI
Immagine generata con AI
Riassunto generato automaticamente tramite intelligenza artificiale:

Python è principalmente un linguaggio interpretato che esegue il codice sorgente a runtime, offrendo portabilità e flessibilità a discapito di un overhead di esecuzione maggiore. Adotta un approccio ibrido con compilazione JIT per ottimizzare le prestazioni delle parti critiche del codice.

Introduzione

Prima di addentrarci nella sintassi di Python è utile chiarire come un linguaggio di programmazione trasforma il codice sorgente (il codice così come lo scrive lo sviluppatore, prima di qualsiasi traduzione) in qualcosa che una macchina può eseguire. Esistono principalmente due strategie.

La prima è la compilazione: un compilatore trasforma l'intero codice sorgente in linguaggio macchina (codice oggetto o assembly) prima dell'esecuzione, producendo un eseguibile. I vantaggi principali sono le elevate prestazioni a runtime e la possibilità di individuare molti errori già in fase di compilazione, prima ancora di eseguire il programma.

La seconda è l'interpretazione: un interprete esegue direttamente il codice sorgente a runtime, istruzione per istruzione. I vantaggi risiedono nella portabilità, nel debugging interattivo e nella flessibilità dinamica; lo svantaggio principale è un overhead di esecuzione più elevato rispetto al codice compilato.

Esistono anche forme ibride, come la compilazione JIT (Just-In-Time): il codice sorgente viene prima tradotto in bytecode, un formato intermedio che non è eseguibile direttamente dalla CPU (a differenza del codice prodotto da una compilazione classica). Una macchina virtuale carica questo bytecode e lo esegue: inizialmente lo interpreta, per garantire un avvio rapido, salvo poi compilare a runtime, in codice macchina nativo, le porzioni di codice identificate come più critiche per le prestazioni durante l'esecuzione stessa.

Approccio Quando avviene la traduzione Punto di forza principale Limite principale
Compilazione Prima dell'esecuzione, una tantum Prestazioni elevate, errori rilevati in anticipo Meno portabilità, ciclo di build più lento
Interpretazione A runtime, istruzione per istruzione Portabilità, debugging interattivo Overhead di esecuzione
JIT A runtime, su bytecode intermedio Avvio rapido con prestazioni che migliorano nel tempo Complessità implementativa della VM

Dove si colloca Python

Python è un linguaggio di programmazione interpretato, dinamico e fortemente tipizzato, che adotta sia il duck typing sia il gradual typing.

Il duck typing è uno stile di tipizzazione dinamica in cui la validità di un oggetto è determinata dai metodi e dagli attributi che espone a runtime, piuttosto che dalla sua classe o dal tipo dichiarato. Il nome deriva dal detto "se cammina come un'anatra e starnazza come un'anatra, allora è un'anatra": non conta cosa un oggetto dichiara di essere, ma cosa è in grado di fare. Python e JavaScript adottano questo approccio: se un oggetto espone un metodo .quack(), può essere usato ovunque sia richiesta un'anatra, indipendentemente dalla gerarchia di classi a cui appartiene.

Il gradual typing è invece un sistema di tipi che permette di mescolare codice tipizzato staticamente e codice tipizzato dinamicamente all'interno dello stesso programma, introducendo un tipo speciale (spesso chiamato Any o dynamic, come any in TypeScript) che funge da "ponte" tra le due modalità: il type checker tratta questo tipo come compatibile con qualunque altro, sospendendo il controllo statico in quel punto specifico del codice. Questo approccio è particolarmente utile per l'adozione incrementale della tipizzazione statica in progetti nati come dinamici: è, ad esempio, il principio alla base della migrazione di codebase JavaScript verso TypeScript, dove è possibile tipizzare i moduli gradualmente senza dover riscrivere tutto in un'unica soluzione. In Python lo stesso principio si applica tramite le annotazioni di tipo (introdotte con le type hint), verificabili staticamente con strumenti come mypy pur restando facoltative a runtime.

Python supporta tutti gli stili di programmazione: imperativo, dichiarativo (ad esempio funzionale) e orientato agli oggetti, ma internamente tutto in Python è un oggetto. Il linguaggio pone una forte enfasi sulla leggibilità del codice, utilizzando un'indentazione significativa per delimitare le definizioni di funzioni, metodi e classi al posto delle parentesi graffe.

Breve storia e versioni

Python è stato ideato alla fine degli anni '80 da Guido van Rossum, che ne ha supervisionato personalmente lo sviluppo per molti anni, e rilasciato pubblicamente per la prima volta nel 1991. Il nome non ha nulla a che vedere con il rettile: deriva dal gruppo comico britannico Monty Python, di cui van Rossum era appassionato. Il linguaggio è oggi gestito, come progetto open source, dalla Python Software Foundation, che riceve proposte di modifica dalla comunità attraverso le Python Enhancement Proposal (PEP).

Le tappe principali della sua evoluzione sono:

Versione Anno Nota
Python 1.x 1991 Prima versione pubblica
Python 2 2000 Ampiamente diffuso, oggi a fine vita
Python 3 2008 Versione di riferimento attuale, non retrocompatibile con Python 2

Per quanto riguarda le implementazioni, CPython è quella di riferimento: scritta in C, è l'implementazione ufficiale adottata dalla maggior parte degli ambienti di sviluppo. Il codice Python viene interpretato sotto forma di bytecode per una macchina virtuale interna. Esistono però implementazioni alternative, come Jython, che genera bytecode eseguibile sulla JVM, permettendo l'interoperabilità con l'ecosistema Java.

Caratteristiche del linguaggio

Python si definisce spesso "multi-paradigma": consente di scrivere codice procedurale/di scripting così come codice orientato agli oggetti, spesso combinando i due stili nello stesso progetto.

È un linguaggio interpretato: il codice non viene compilato in un eseguibile separato ed esiste un ambiente interattivo (la REPL) in cui è possibile eseguire istruzioni una per una e osservarne subito il risultato.

È inoltre un linguaggio "batteries-included": la libreria standard è estremamente ampia e copre la maggior parte delle esigenze comuni (gestione di file, rete, strutture dati, ecc.), risultando comunque facilmente estensibile tramite pacchetti di terze parti.

Sul fronte della tipizzazione, le variabili si usano senza dichiararle esplicitamente e possono cambiare il tipo di oggetto a cui fanno riferimento nel corso dell'esecuzione, è questo il significato di "dinamicamente tipizzato". La sintassi, infine, è volutamente compatta e non appartiene alla famiglia sintattica del C: l'indentazione stessa indica la nidificazione dei blocchi di codice, al posto delle parentesi graffe.

Queste caratteristiche hanno reso Python particolarmente popolare per la prototipazione rapida, e in particolare per applicazioni di data analytics, che richiedono l'esplorazione, la trasformazione e l'integrazione di dati provenienti da sorgenti diverse, oltre allo sviluppo di modelli di analisi.

Installazione e gestione dell'ambiente

Python è preinstallato nella maggior parte degli ambienti operativi moderni; il comando tipico per invocarlo è python3 (su Windows spesso python3.exe). Dove non fosse già presente, l'installazione può avvenire tramite i package manager di sistema (ad esempio Homebrew su macOS o Chocolatey su Windows).

Uno script Python è tipicamente contenuto in un file con estensione .py (ad esempio primo.py) ed eseguito con python3 primo.py. In alternativa, lanciando python senza argomenti si accede alla modalità interattiva: un prompt di comandi in cui è possibile eseguire le istruzioni una per una. In questa modalità esiste una variabile implicita, denominata _, che mantiene automaticamente il risultato dell'ultima operazione eseguita; per uscire si utilizza quit() oppure la combinazione ctrl-d.

Il package manager pip

pip è il package manager ufficiale di Python: gestisce l'installazione di pacchetti prelevandoli dal Python Package Index, disponibile all'indirizzo https://pypi.org/. Il comando tipico è pip o pip3. Per lo sviluppo di base pip è raramente indispensabile, perché la libreria standard di Python è già molto ampia (in questo Python è simile a Java).

Concretamente, pip cerca il pacchetto richiesto sul Python Package Index, ne scarica l'archivio (tipicamente in formato .tar.gz o .whl), ne risolve le dipendenze dichiarate e infine lo installa nella directory dei pacchetti dell'ambiente Python attivo. Per ispezionare dove un pacchetto è stato installato si può usare pip show <nome_pacchetto>.

Ambienti virtuali con venv

La buona prassi è dare a ogni progetto le proprie dipendenze isolate e, idealmente, anche la propria versione di Python. Lo strumento standard per farlo è venv, incluso nella libreria standard a partire da Python 3.3 e che non richiede alcuna installazione aggiuntiva. Per esigenze più avanzate, o per progetti ancora su Python 2, restano disponibili alternative come virtualenv o Anaconda.

L'uso tipico prevede due passaggi: la creazione dell'ambiente e la sua attivazione, che varia in base al sistema operativo.

Windows

python3 -m venv nome_ambiente nome_ambiente\Scripts\activate.bat

macOS / Linux

python3 -m venv nome_ambiente source nome_ambiente/bin/activate

Una volta attivato l'ambiente, tutti i pacchetti installati con pip vengono collocati nella cartella locale nome_ambiente, senza toccare l'installazione globale di Python. Per uscire dall'ambiente virtuale è sufficiente il comando deactivate.

Errori comuni

pip install sembra funzionare ma l'import del modulo fallisce

Nella maggior parte dei casi l'ambiente virtuale non è attivo: il pacchetto è stato installato a livello globale (o in un altro ambiente), mentre lo script viene eseguito con l'interprete di un ambiente diverso. Verificare che l'ambiente corretto sia attivo prima di lanciare lo script.

Chiamare un metodo restituisce un TypeError sul numero di argomenti

È l'errore più comune per chi arriva da altri linguaggi: un metodo di istanza deve sempre avere self come primo parametro nella sua firma, perché Python lo passa automaticamente ogni volta che il metodo viene invocato tramite un'istanza. Dimenticarlo genera un errore sul numero di argomenti attesi.

Panoramica dei tipi di dato

Python è un linguaggio fortemente tipizzato: ogni valore ha un tipo ben definito, anche se la variabile che lo referenzia non viene mai dichiarata esplicitamente. Ecco una panoramica sintetica dei tipi fondamentali.

Categoria Tipo Mutabilità Descrizione
Numerico int, float, complex Immutabile Interi a precisione arbitraria, numeri in virgola mobile, numeri complessi
Booleano bool Immutabile True o False; tecnicamente una sottoclasse di int (True == 1, False == 0)
Testuale str Immutabile Sequenze di caratteri Unicode
Sequenza list, tuple, range list mutabile; tuple e range immutabili Collezioni ordinate di elementi
Insieme set, frozenset set mutabile; frozenset immutabile Collezioni non ordinate, senza duplicati
Mappa dict Mutabile Coppie chiave-valore
Speciale NoneType Rappresenta l'assenza di un valore (None)
Chiamabile function, class Funzioni, lambda e classi sono oggetti di prim'ordine, chiamabili con ()

Variabili e costanti

In Python non si usano parole chiave per creare variabili o costanti (a differenza, ad esempio, di Java, dove si scriverebbe String variabile = "variabile";). È possibile associare un nome a qualsiasi oggetto tramite l'operatore di assegnazione =, con la sintassi <nome> = <valore>.

Una variabile può essere riassegnata più volte nel corso del programma, anche a oggetti di tipo diverso a ogni riassegnazione:

mia_variabile = 1
mia_variabile = 2
print(mia_variabile)
print(type(mia_variabile))

Le costanti sono invece nomi pensati per essere assegnati una sola volta. Per convenzione si dichiarano a livello di modulo, così da risultare visibili a tutte le funzioni e classi del programma, e si scrivono in SCREAMING_SNAKE_CASE:

MIA_COSTANTE = "Stringa immutabile"
print(MIA_COSTANTE)
print(type(MIA_COSTANTE))

Va precisato che si tratta di una convenzione, non di un vincolo imposto dal linguaggio: Python non offre un meccanismo nativo per impedire la riassegnazione di un nome scritto in maiuscolo. È qui utile richiamare il concetto di scope (ambito di visibilità): la regione di codice in cui una variabile, funzione o identificatore è accessibile e utilizzabile, ovvero "dove" quel nome ha significato all'interno del programma.

Come da convenzione PEP 8, le variabili si scrivono sempre in snake_case e le costanti in SCREAMING_SNAKE_CASE.

Funzioni, procedure e metodi

Una funzione è un blocco di codice riutilizzabile che compie un compito specifico, evitando di ripetere lo stesso codice più volte in modo lineare. Anche le funzioni, in Python, sono oggetti: possono essere assegnate a una variabile, passate come argomento ad altre funzioni, restituite come valore di ritorno (callback) o memorizzate in strutture dati come liste o dizionari. È il concetto di funzioni di primo ordine (first-class citizen): le funzioni godono dello stesso status di qualsiasi altro valore del linguaggio, un requisito fondamentale per abilitare paradigmi come la programmazione funzionale, che fa uso massiccio di funzioni come map o filter.

In gergo, le funzioni che restituiscono un valore vengono chiamate semplicemente funzioni, mentre quelle che non restituiscono nulla sono dette procedure. Le funzioni legate a una classe si chiamano invece metodi. Funzioni e metodi possono essere raggruppati in file o moduli e importati, integralmente o in parte, in altri programmi.

In Python, anche le procedure restituiscono implicitamente l'oggetto None quando l'esecuzione giunge alla fine della funzione o incontra un return senza argomenti.

La parola chiave def introduce la definizione, seguita dal nome della funzione, dai due punti e dall'eventuale elenco di parametri e corpo della funzione: def <nome_funzione>(...):.

def somma(a, b):
    totale = a + b
    print(totale)
    return
somma(1, 1)

Questo è un esempio di procedura: non restituisce alcun valore esplicito. La versione seguente, invece, è una vera funzione, perché restituisce esplicitamente il risultato tramite la parola chiave return:

def somma(a, b):
    totale = a + b
    return totale
print(somma(1, 1))

Quando la stessa logica è legata a una classe, si parla di metodo. È importante ricordare che un metodo di istanza deve sempre includere self come primo parametro: Python lo passa automaticamente ogni volta che il metodo è invocato tramite un'istanza.

class Calcolatrice:
    def somma(self, a, b):
        return a + b
calcolatrice = Calcolatrice()
totale = calcolatrice.somma(1, 1)
print(totale)

Il significato pieno di questo esempio richiede i concetti di programmazione a oggetti, che verranno approfonditi in un articolo successivo del corso; per ora basta osservare come si definisca e si invochi il metodo somma. Le funzioni si invocano tramite il proprio nome seguito da (); la notazione a punto (.) si usa invece per chiamare funzioni definite all'interno di una classe o di un modulo, anche non definiti da noi:

testo = "Questa è una stringa di prova."
testo.upper() # Risultato: QUESTA È UNA STRINGA DI PROVA.

Un accenno alle classi come placeholder

In Python il corpo di un blocco indentato (funzione, classe, ciclo) non può mai essere vuoto: se non c'è ancora logica da inserire, si usa la parola chiave pass come segnaposto sintattico:

class Calcolatrice:
    pass
calc = Calcolatrice()

Commenti e docstring

I commenti in Python iniziano sempre con #. A differenza di molti altri linguaggi, Python non prevede una sintassi dedicata ai commenti multi-riga: ogni riga di un blocco di commento deve iniziare con #. I commenti vengono completamente ignorati dall'interprete.

La prima istruzione del corpo di una funzione, di un metodo o di una classe può facoltativamente essere una docstring, dichiarata con tre apici doppi (""") allineati allo stesso livello di indentazione del blocco:

def somma(a, b):
    """Calcola la somma di due numeri.
    Args:
        a (int | float): Il primo addendo.
        b (int | float): Il secondo addendo.
    Returns:
        int | float: La somma di a e b.
    """
    return a + b

Essendo Python un linguaggio fortemente tipizzato, è preferibile evitare di ripetere il tipo nella docstring: conviene dichiararlo nella firma della funzione, ovvero nell'insieme di informazioni che ne descrivono nome e utilizzo senza dover leggere il corpo o l'implementazione. Per molti sviluppatori è l'approccio più pulito, perché evita duplicazione tra annotazioni di tipo e documentazione.

La firma diventa quindi:

def somma(a: int | float, b: int | float) -> int | float:

Il simbolo -> prima dei due punti indica all'interprete (e agli strumenti di analisi statica) che la funzione restituisce un intero o un numero in virgola mobile; entrambi i parametri accettano indifferentemente uno dei due tipi. La funzione completa, con docstring semplificata, diventa:

def somma(a: int | float, b: int | float) -> int | float:
    """Calcola la somma di due numeri."""
    return a + b

Le docstring vengono lette da strumenti di documentazione automatizzati e possono essere recuperate a runtime tramite l'attributo speciale .__doc__, richiamabile sul nome della funzione, del metodo o della classe. Il loro utilizzo è consigliato per programmi di qualsiasi dimensione in cui sia necessaria documentazione; le relative convenzioni di stile sono definite nella PEP 257, così come le convenzioni generali di stile del codice sono definite nella PEP 8.

Le docstring possono anche fungere da test unitari leggeri (doctest), eseguibili sia tramite PyTest sia importando il modulo doctest della libreria standard, un argomento che verrà approfondito in un articolo dedicato al testing.

Come proseguirà il modulo

Questo primo articolo introduce Python seguendo una visione volutamente parziale e procedurale del linguaggio, utile per fissare i concetti di base (variabili, funzioni, commenti, docstring) senza richiedere ancora nozioni di programmazione a oggetti. I prossimi passi del percorso saranno:

  • la programmazione a oggetti in Python, che richiede la conoscenza dei concetti fondamentali della POO;
  • le tecniche avanzate di programmazione in Python, una volta consolidate le basi procedurali e a oggetti.

Per chi desidera approfondire da subito, restano utili riferimenti come il tutorial ufficiale su https://docs.python.org/3/tutorial/index.html e la guida introduttiva su https://www.python.org/about/gettingstarted/, oltre alle convenzioni di stile definite in PEP 8 (https://peps.python.org/pep-0008/) e PEP 257 (https://peps.python.org/pep-0257/).

Articolo scritto con il supporto di LLM per la formattazione e la struttura del codice.

Commenti