I materiali

Contenuto del capitolo
  • Cosa sono i dati
  • Cosa sono le variabili
  • I tipi di dato e come si usano
  • Come definire i propri tipi di dato

Introduzione

In ogni attività creativa abbiamo bisogno di tre ingredienti fondamentali: gli strumenti, i materiali e le tecniche. Ad esempio per dipingere occorrono pennelli, matite e tavolozze. Le tecniche sono cose come acquerello, affresco, olio. I materiali infine sono i colori, la carta e l'acqua. Analogamente nella programmazione gli strumenti sono i linguaggi di programmazione, i sistemi operativi e il computer. Le tecniche sono i comandi di programmazione che abbiamo trattato nel capitolo precedente ed i materiali sono i dati che vogliamo manipolare. In questo capitolo tratteremo i "materiali" della programmazione.

Questo capitolo è piuttosto lungo e a causa della sua natura potrete trovarlo un po' arido, ma in compenso non è necessario che lo leggiate tutto di seguito in una sola volta. Il capitolo inizia mostrando i tipi di dato fondamentali, quindi procede mostrando come trattare insiemi o collezioni di dati ed infine si occupa di argomenti più avanzati. È possibile interrompere la lettura dopo la parte sulle collezioni di dati, proseguire con un paio dei capitoli successivi e tornare in seguito alla parte finale quando useremo gli elementi più complessi.

I dati

Dati è un termine che tutti usano, ma che pochi realmente comprendono. Il dizionario lo definisce come:

"Fatti o cifre da cui è possibile trarre conclusioni; informazioni"

Questo non ci aiuta molto, ma almeno ci fornisce un punto di partenza. Vediamo se è possibile chiarire le idee mostrando come si usano i dati nella programmazione. I dati sono le "cose", le informazioni elementari, che i programmi manipolano. Senza dati un programma non può svolgere alcuna funzione utile. I programmi trattano i dati in molti modi, spesso a seconda del loro tipo. Ogni tipo di dato ha un insieme di operazioni, azioni che si possono fare con il dato. Ad esempio abbiamo visto che possiamo sommare fra loro i numeri. La somma è una operazione sui dati di tipo numerico. I dati sono di molti tipi diversi e noi vedremo quelli più comuni e le operazioni disponibili per ciascun tipo.

Le variabili

I dati sono immagazzinati nella memoria del computer. Potete immaginarvela come la parete di cassette usate negli uffici postali per smistare la posta. È possibile mettere una lettera in una cassetta, ma questo serve a poco se le cassette non sono etichettate con l'indirizzo di destinazione. Le variabili sono le etichette delle cassette che costituiscono la memoria del computer.

Capire che cosa sono i dati è importante, ma per manipolarli dobbiamo sapere dove sono e per questo si usano le variabili. In termini di programmazione possiamo creare istanze di dati di un certo tipo ed associarle a variabili. Una variabile è un riferimento ad un'area specifica che si trova in qualche punto della memoria del computer. Queste aree contengono i dati. In alcuni linguaggi di programmazione il tipo di una variabile deve coincidere con il tipo di dato cui si riferisce. Ogni tentativo di assegnare un dato di tipo diverso a tale variabile causerà un errore. Alcuni programmatori preferiscono questo sistema, detto tipizzazione statica, perché consente di evitare alcuni sottili errori che sono poi difficili da individuare.

In Python una variabile assume il tipo del dato che le viene assegnato. In seguito se cercheremo di mescolare dati di tipo diverso in modo strano, ad esempio sommando una stringa ad un numero, otterremo un avviso di errore. (Ricordate il messaggio di errore dell'esempio? Era proprio di questo tipo). Possiamo cambiare il tipo di dato assegnato ad una variabile, semplicemente assegnandole un altro dato.

>>> q = 7         # q adesso è un numero
>>> print q
7
>>> q = "Sette"   # q viene associato ad una stringa
>>> print q
Sette

Notate che q viene inizialmente associato al numero 7; l'associazione si mantiene fino a che non lo riassociamo alla stringa di caratteri "Sette". Cioè le variabili di Python assumono il tipo del dato cui si riferiscono, ma possiamo cambiare il dato cui si riferiscono semplicemente ripetendo l'associazione. A questo punto il dato originale si "perde" e Python lo cancellerà dalla memoria (a meno che non ci sia un'altra variabile cui è associato) questo meccanismo viene chiamato "garbage collection" (raccolta dei rifiuti, N.d.T.).

Il "garbage collection" può essere visto come il lavoro dell'impiegato postale che ogni tanto raccoglie tutti i pacchetti che si trovano nelle scatole prive di etichetta. Se non è in grado di trovare un proprietario o un indirizzo scritto sul pacchetto li getta semplicemente fra i rifiuti. Ma vediamo alcuni esempi di tipi di dato e di come tutto ciò interagisce.

Tipi di dato primitivi

I tipi di dato primitivi si chiamano cosí in quanto sono i più elementari tipi di dato che possiamo manipolare. I tipi più complessi sono in effetti combinazioni di quelli primitivi. Questi sono i blocchi elementari mediante i quali possono essere costruiti tutti gli altri tipi, le vere fondamenta della programmazione. Essi includono le lettere, i numeri e altri oggetti detti tipi booleani.

Le stringhe di caratteri

Queste le abbiamo già incontrate. Sono costituite in sostanza da qualunque stringa o sequenza di caratteri che possono essere visualizzati sullo schermo. (In realtà esistono anche caratteri non visualizzabili o caratteri di controllo).

In Python le stringhe possono essere rappresentate in vari modi:

Con apici semplici:

'Ecco una stringa'

Con apici doppi:

"Ecco una stringa assai simile"

Con apici doppi ripetuti tre volte:

""" Ecco una stringa molto lunga che
    se vogliamo puo' occupare parecchie righe e Python
    mantiene la divisione in righe come l'abbiamo scritta ..."""

Un uso particolare dell'ultima forma si ha nella possibilità di creare la documentazione per funzioni Python scritte in proprio. Torneremo su questo punto più avanti.

È possibile accedere ai singoli caratteri di una stringa se la trattiamo come un vettore di caratteri (si veda il paragrafo sui vettori, più avanti). Di solito ci sono anche operazioni fornite dal linguaggio di programmazione per la manipolazione delle stringhe: trovare una sottostringa, unire due stringhe, copiare una stringa in un'altra, ecc.

Operatori su stringhe

Ci sono varie operazioni che possono essere effettuate sulle stringhe. Alcune di queste sono incorporate in Python, altre sono fornite sotto forma di moduli che devono essere importati (come abbiamo fatto per il modulo sys nel capitolo sulle sequenze semplici).

Operatori su stringhe

Operatore Descrizione
S1 + S2 Concatenazione di S1 ed S2
S1 * N N ripetizioni di S1

Possiamo vederli in azione nei seguenti esempi:

>>> print 'Ancora ed ' + 'ancora'    # concatenazione di stringhe
Ancora ed ancora
>>> print 'Ripeti ' * 3		        # ripetizione di una stringa
Ripeti Ripeti Ripeti
>>> print 'Ancora ' + ('ed ancora ' * 3)  # combinazione di '+' e '*'
Ancora ed ancora ed ancora ed ancora

Possiamo anche assegnare stringhe a variabili:

>>> s1 = 'Ancora '
>>> s2 = 'ed ancora '
>>> print s1 + (s2 * 3)
Ancora ed ancora ed ancora ed ancora

Notate che gli ultimi due esempi producono lo stesso risultato.

Variabili stringa in BASIC

In BASIC se una variabile è di tipo stringa dovete terminare il suo nome con un carattere $. Dopo aver fatto ciò non è possibile assegnare un valore numerico alla variabile. Analogamente se la variabile è intera (termina con %) non è possibile assegnarle una stringa. Il BASIC ammette l'uso di "variabili anonime" che non terminano in nessun modo speciale. Queste però possono contenere solo numeri, sia reali che interi, ma solo numeri. Ecco un esempio di variabile stringa in BASIC:
DIM LaMiaStringa$

LaMiaStringa$ = "Ciao a tutti!"

PRINT LaMiaStringa$

Stringhe in Tcl

Il TCl usa internamente le stringhe per qualunque cosa. Dal punto di vista dell'utilizzatore però questo non è sempre evidente. Quando si utilizza esplicitamente una stringa, questa deve essere racchiusa fra apici doppi. Per assegnare un valore ad una variabile in Tcl si usa il comando set e per leggere una variabile stringa (anzi ogni tipo di variabile in Tcl) occorre premettere un "$" al nome, ad esempio:

% set LaMiaStringa "Ciao a tutti"
% put $LaMiaStringa

Note: sia in Tcl che in BASIC si possono usare solo gli apici doppi per le stringhe.

Interi

Sono numeri interi che possono variare da un valore negativo molto grande ad un valore positivo molto grande. Questo è un punto importante da non dimenticare. Di solito non pensiamo ai numeri come se fossero di grandezza limitata, ma in un computer esistono limiti superiori ed inferiori. Il valore del limite superiore viene indicato con MAXINT e dipende dal numero di bit usati dal vostro computer per rappresentare i numeri. Nella maggior parte dei computer attuali il numero di bit è 32, cosí MAXINT è pari a circa 2 miliardi.

Numeri che possono assumere valori sia positivi che negativi sono detti interi con segno. È possibile anche utilizzare interi senza segno che sono limitati ai soli valori positivi più lo zero. Ciò significa che esiste un valore massimo raggiungibile che è circa 2 * MAXINT, ovvero 4 miliardi su un computer a 32 bit, in quanto si può usare lo spazio che prima serviva a rappresentare il segno per rappresentare altri numeri positivi.

Poiché gli interi sono limitati al valore di MAXINT, se si sommano due interi il cui totale sarebbe maggiore di MAXINT, il risultato risulta errato. In alcuni sistemi o linguaggi di programmazione il valore errato viene semplcemente riportato cosí com'é (di solito con qualche tipo di segnalazione nascosta che potete verificare nel caso in cui pensiate che si sia presentato il problema). Normalmente viene attivata una condizione di errore e, se il programma non bada ad intercettarla viene terminato. Python adotta quest'ultima tecnica mentre Tcl usa quella precedente. Il BASIC genera un errore, ma non c'è modo di intercettarlo (almeno io non ne conosco!).

Operatori aritmetici

Abbiamo già trattato gran parte degli operatori di cui potete aver bisogno nel capitolo "Semplici sequenze di comandi", comunque per riassumere:

Operatori aritmetici e bit a bit

EsempioDescrizione
M + NSomma di M e N
M - NSottrazione di N da M
M * NProdotto di M ed N
M / NDivisione, con risultato intero o reale a seconda del tipo di M ed N. Se M o N sono numeri reali (vedi sotto), il risultato è reale.
M % NModulo: il resto della divisione intera di M per N
M**NElevamento a potenza: M elevato alla potenza N

L'ultima operazione compare per la prima volta, vediamo quindi un esempio di creazione di qualche variabile intera e di uso dell'operatore di elevamento a potenza:

>>> i1 = 2     # crea un intero ed assegna il valore a i1
>>> i2 = 4
>>> i3 = 2**4  # assegna il risultato di 2 elevato alla potenza 4 ad i3
>>> print i3
16

Interi in BASIC

Il BASIC ha alcune regole aggiuntive relativamente agli interi. Per dichiarare una variabile intera in BASIC è possibile usare un semplice nome senza caratteri speciali oppure si può segnalare al BASIC che vogliamo effettivamente memorizzare un intero (questo risulta leggermente più efficiente). Per farlo dobbiamo terminare il nome con il carattere "%":

FOO = 8   REM FOO puo` contenere ogni tipo di numero
BAR% = 9  REM BAR puo` contenere solo interi

Infine una stranezza relativa alle variabili intere in BASIC:

i% = 7
PRINT 2 * i%
i% = 4.5
PRINT 2 * i%

Notate che l'assegnazione di 4.5 ad i% sembra funzionare, ma il valore assegnato è solo la parte intera. Questo ricorda il modo in cui Python tratta la divisione fra interi. Tutti i linguaggi di programmazione hanno qualche piccola idiosincrasia come questa!

I numeri in Tcl

Come abbiamo detto in precedenza Tcl memorizza al suo interno tutti gli oggetti come stringhe, tuttavia questo non fa molta differenza per l'utente in quanto Tcl converte di nascosto i valori in numeri e viceversa quando serve. Quindi le restrizioni sulla dimensione dei numeri rimangono valide.

L'uso dei numeri in Tcl è leggermente più complesso che nella maggioranza degli altri linguaggi perché in tali occasioni è necessario segnalare all'interprete che si tratta di un calcolo, mediante il comando expr:

% put [expr 6 + 5]
11

Tcl individua le parentesi quadre e valuta la parte racchiusa in esse prima di tutto, come se fosse stata scritta sulla riga di comando. Nel fare questo individua il comando expr ed effettua il calcolo. Il risultato viene quindi visualizzato sullo schermo da put. Se cercate di usare put direttamente sulla somma Tcl si limita a visualizzare "6 + 5":

% put 6 + 5
6 + 5

I numeri reali

Sono i numeri con la "virgola". Possono anche rappresentare numeri molto grandi, molto maggiori di MAXINT, ma con minore precisione. Ciò significa che due numeri reali che dovrebbero essere identici possono non sembrare tali se comparati mediante un computer. Questo accade perché il computer usa valori approssimati per i numeri. Quindi 4.0 potrebbe essere rappresentato dal computer come 3.9999999... oppure come 4.0000...01. Queste approssimazioni sono abbastanza precise per la maggior parte degli scopi, ma talvolta possono diventare importanti! Se ottenete un risultato strano usando i numeri reali, ricordatevi di questo fatto.

I numeri in virgola mobile hanno le stesse operazioni degli interi con in più la possibilità di troncare un numero alla parte intera.

Numeri complessi e immaginari

Se avete conoscenze scientifiche o matematiche potreste domandarvi cosa succede con i numeri complessi. In caso contrario potreste non aver mai sentito parlare di numeri complessi. In ogni caso alcuni linguaggi di programmazione, incluso Python, forniscono il supporto di base per il tipo complesso, mentre altri forniscono una libreria di funzioni che operano su numeri complessi. Anticipando una vostra possibile domanda: questo vale anche per le matrici.

In Python un numero complesso viene rappresentato come:

(reale+immaginarioj)

Quindi una semplice somma di numeri complessi risulta:

>>> M = (2+4j)
>>> N = (7+6j)
>>> print M + N
(9+10j)

Tutte le operazioni su interi si applicano anche ai numeri complessi.

Valori booleani - Vero e Falso

Come si capisce dal titolo questo tipo può assumere solo due valori: vero o falso. Alcuni linguaggi supportano direttamente valori booleani, altri usano una convenzione per cui qualche valore numerico (solitamente 0) rappresenta il falso ed un altro (spesso 1 o -1) rappresenta vero.

I valori booleani vengono chiamati talvolta "valori di verità" in quanto sono usati per verificare se qualche cosa è vera oppure no. Ad esempio se scrivete un programma per effettuare il salvataggio di tutti i file di una directory, potreste salvare ogni singolo file e quindi interrogare il sistema operativo per conoscere il nome del file successivo. Se non ci sono altri file da salvare otterrete una stringa vuota. Potete allora verificare se il nome è una stringa vuota e memorizzare il risultato in una variabile booleana (valore vero se la stringa è vuota). Vedremo in seguito come usare questo risultato.

Operatori booleani (o logici)

EsempioDescrizioneRisultato
A and BCongiunzioneVero se A e B sono entrambi veri, altrimenti falso.
A or BDisgiunzioneVero se uno dei due o entrambi A e B sono veri. Falso se entrambi sono falsi
A == BEguaglianzaVero se A è uguale a B
A != B
oppure
A <> B
DiseguaglianzaVero se A NON è uguale a B.
not BNegazioneVero se B è falso

Nota: la negazione utilizza un solo operando, tutte le altre operazioni due.

Collezioni

L'informatica ha costruito un'intera disciplina attorno allo studio delle collezioni e dei loro vari comportamenti. In qualche caso le collezioni sono chiamate contenitori. In questo paragrafo esamineremo prima di tutto le collezioni supportate da Python, poi concluderemo con un breve esame di qualche altro tipo di collezione che potrete incontrare in altri linguaggi.

Collezioni in Python

Liste

Una lista è una sequenza di elementi. Ciò che la differenzia da un vettore è che essa può crescere indefinitamente, basta aggiungere elementi. Però di solito non è indicizzata, quindi per trovare un elemento dovete attraversarla dall'inizio alla fine verificando per ogni elemento se si tratta di quello voluto. Sia Python che Tcl supportano le liste. Il BASIC no ed occorre usare qualche trucco di programmazione per simularle. I programmatori BASIC di solito semplicemente creano vettori molto grandi. Python consente anche di usare indici sulle liste. Come vedremo questa è una caratteristica assai utile.

Operazioni su liste

Python fornisce molte operazioni sulle collezioni. Quasi tutte si applicano alle liste ed un sottoinsieme di esse ad altri tipi di collezione, incluse le stringhe, che sono solo uno speciale tipo di liste di caratteri. Per creare una lista ed accedervi in Python si utilizzano le parentesi quadre. Potete creare una lista vuota usando una coppia di parentesi quadre con niente al loro interno, oppure creare una lista con qualche contenuto separando i valori fra parentesi con le virgole:

>>> unaLista = []
>>> unAltra = [1,2,3]
>>> print unAltra
[1, 2, 3]

Possiamo accedere ai singoli elementi usando un numero di indice, con il primo elemento con indice 0, all'interno delle parentesi quadre:

>>> print unAltra[2]
3

Possiamo anche cambiare i valori degli elementi di una lista in modo simile:

>>> unAltra[2] = 7
>>> print unAltra
[1, 2, 7]

Potete usare indici negativi per accedere agli elementi a partire dal fondo della lista. Il caso più comune è usare -1 per avere l'ultimo elemento:

>>> print unAltra[-1]
7

Possiamo anche aggiungere nuovi elementi al fondo di una lista usando l'operatore append():

>>> unaLista.append(42)
>>> print unaLista
[42]

Possiamo anche inserire una lista all'interno di un'altra, quindi se appendiamo la nostra seconda lista alla prima:

>>> unaLista.append(unAltra)
>>> print unaLista
[42, [1, 2, 7]]

Notate che il risultato è una lista con due elementi, ma il secondo elemento è a sua volta una lista (come si deduce dalle [ ] che la circondano). Questa caratteristica è utile in quanto consente di costruire rappresentazioni di tabelle o griglie usando una lista di liste. Possiamo quindi accedere all'elemento 7 usando un doppio indice:

>>> print unaLista[1][2]
7

Il primo indice, 1, estrae il secondo elemento che è a sua volta una lista. Il secondo indice, 2, estrae il terzo elemento della sottolista.

L'opposto di aggiungere elementi è, ovviamente, rimuoverli. Per farlo usiamo il comando del:

>>> del unaLista[1]
>>> print unaLista
[42]

Se vogliamo unire due liste per ottenerne una possiamo usare lo stesso operatore di concatenazione "+" che abbiamo visto per le stringhe:

>>> nuovaLista = unaLista + unAltra
>>> print nuovaLista
[42, [1, 2, 7], 1, 2, 7]

Allo stesso modo possiamo applicare l'operatore di ripetizione per riempire una lista con molte copie dello stesso valore:

>>> listaZero = [0] * 5
>>> print listaZero
[0, 0, 0, 0, 0]

Infine, possiamo determinare la lunghezza di una lista usando la funzione incorporata len():

>>> print len(unaLista)
2
>>> print len(listaZero)
5

Le liste in Tcl

Anche Tcl ha un tipo lista di base ed una varietà di comandi per operare su queste liste. Questi comandi sono identificabili grazie al prefisso "l", ad esempio linsert, lappend, lindex, ecc. Ecco un esempio di creazione di una semplice lista e di accesso ad un suo elemento in Tcl:

% set L [list 1 2 3]
% put [lindex $L 2]
3

Le "tuple"

Non tutti i linguaggi forniscono un costrutto per le "tuple" ma in quelli che lo prevedono risulta estremamente utile. Una tupla è in realtà una collezione arbitraria di valori che possono essere trattati come una unità. Una tupla è per molti versi simile ad una lista, ma con l'importante differenza che le tuple sono immutabili ovvero non potete cambiarle, né aggiungere elementi una volta che sono state create. In Python le tuple si rappresentano semplicemente come liste di valori separati da virgola racchiuse fra parentesi tonde, cosí:

>>> unaTupla = (1,3,5)
>>> print unaTupla[1]    # si usano gli indici come per le liste
3
>> unaTupla[2] = 7       # errore, non si puo` modificare un elemento di una tupla
Traceback (innermost last):
  File "<pyshell >", line 1, in ?
  	unaTupla[2] = 7
TypeError: object doesn't support item assignment

Gli aspetti principali da ricordare sono che, mentre si usano parentesi tonde per definire la tupla, si usano parentesi quadre per l'indice e che una tupla non può essere modificata una volta creata. A parte questo gran parte delle operazioni su liste sono anche applicabili alle tuple.

Memorie associative (in inglese: "dictionary" o "hash", N.d.t.)

Una memoria associativa, o dizionario, come il nome suggerisce, contiene un valore associato ad una chiave, allo stesso modo in cui un dizionario di parole associa un significato ad una parola. Il valore può essere recuperato per mezzo di un "indice" che costituisce la chiave. Differentemente dai dizionari cartacei la chiave non deve essere necessariamente una stringa di caratteri (anche se spesso lo è) ma può essere un qualunque tipo immutabile, inclusi numeri e tuple. Analogamente i valori associati con le chiavi possono essere qualunque tipo di dato valido per Python. Le memorie associative sono spesso implementate internamente usando una particolare tecnica di programmazione detta tabella hash. Per questo motivo le memorie associative vengono anche dette "hash". La cosa non ha niente a che vedere con la droga! (nell'uso comune "hash" è anche l'abbreviazione di "hashish", N.d.t.).

Poiché l'accesso ai valori del dizionario avviene tramite la chiave è possibile introdurre solo elementi con chiave univoca. I dizionari sono strutture estremamente utili fornite da Python come tipo di base sebbene in altri linguaggi sia necessario usare un modulo o anche crearli personalmente. Possiamo usare i dizionari in molti modi e vedremo molti esempi nel seguito, ma per ora vediamo come creare un dizionario in Python, come introdurvi valori e come leggerli:

>>> diz = {}
>>> diz['boolean'] = "Valore che puo` essere vero oppure falso"
>>> diz['integer'] = "Numero intero"
>>> print diz['boolean']
Valore che puo` essere vero oppure falso

Notate che si usano le parentesi graffe per creare un dizionario e le parentesi quadre per assegnare e leggere i valori.

A causa della loro struttura interna i dizionari non supportano molti degli operatori sulle collezioni che abbiamo visto fino ad ora. Le operazioni di concatenazione, ripetizione, aggiunta di un elemento non funzionano. Per accedere più facilmente alle chiavi del dizionario possiamo usare la funzione keys(), che riporta una lista contenente tutte le chiavi del dizionario.

Se vi sentite sazi a questo punto potete saltare al prossimo capitolo. Non dimenticate di ritornare e terminare questo quando incontrerete tipi che non sono stati trattati fino a questo punto.

Altri tipi di collezioni

Vettori o matrici

Una lista di elementi che sono dotati di indici per poterli recuperare in modo facile e rapido. Di solito dovrete specificare all'inizio quanti elementi volete memorizzare. Supponiamo di avere un vettore di nome A, allora possiamo estrarre il terzo elemento di A scrivendo A[3]. I vettori sono tipi fondamentali in BASIC e di fatto sono l'unico tipo di collezione disponibile. In Python i vettori sono simulati usando liste ed in Tcl sono implementati usando dizionari.

Ecco un esempio di vettore in BASIC:

DIM MioVettore(20) REM Crea un vettore di 20 elementi

MioVettore(1) = 27
MioVettore(2) = 50
FOR i =1 TO 5 
   PRINT MioVettore(i)
NEXT i

Notate che l'indice in BASIC inizia da 1, questo non è usuale ed in molti linguaggi gli indici iniziano da 0. Non ci sono altre operazioni sui vettori, tutto ciò che è possibile fare è crearli, assegnare valori e leggere valori.

Pile (in inglese: stack N.d.t.)

Immaginate una pila di vassoi in un ristorante. Un cameriere mette una pila di altri vassoi puliti in cima e questi vengono presi dai clienti uno alla volta dalla cima. I vassoi in fondo alla pila vengono usati per ultimi (e meno degli altri!). Le pile di dati funzionano allo stesso modo: potete mettere (in inglese push, N.d.t.) un dato sulla pila o estrarlo (pop, N.d.t.) sempre dalla cima. Il dato estratto è sempre l'ultimo memorizzato. Questa proprietà delle pile è talvolta chiamata LIFO (sigla per Last In First Out, l'ultimo immesso è il primo ad uscire, N.d.t.). Un'utile proprietà delle pile consiste nella possibilià di invertire una lista di elementi immettendola in una pila e riestraendola. Il risultato sarà la lista originale con l'ordine degli elementi invertito. Le pile non sono tipi fondamentali in Python, Tcl ed in BASIC. Dovete scrivere qualche riga di programma per implementare questo comportamento. Le liste sono il miglior punto di partenza in quanto, come le pile, possono crescere al bisogno.

Contenitori

Un contenitore è una collezione di elementi senza un ordine specificato e che può contenere duplicati. I contenitori hanno solitamente operatori che consentono di aggiungere, trovare e rimuovere elementi. In Python e Tcl i contenitori sono semplicemente liste. In BASIC potete costruire un contenitore usando un vettore di grandi dimensioni.

Insiemi

Un insieme ha la proprietà di memorizzare solo un campione di ciascun elemento. Solitamente potete verificare se un elemento è contenuto in un insieme (appartenenza), aggiungere, rimuovere e leggere elementi e unire due insiemi in vari modi corrispondenti alle operazioni della teoria degli insiemi in matematica (unione, intersezione, ecc.). Nessuno dei linguaggi che stiamo trattando implementa gli insiemi direttamente, ma in Python ed in Tcl è facile implementarli usando il tipo dizionario.

Code

Una coda è simile ad una pila tranne che il primo elemento inserito in una coda è anche il primo ad essere estratto. Questo è conosciuto come comportamento FIFO (sigla per First In First Out, il primo ad entrare è il primo ad uscire, N.d.t.).

Esistono un gran numero di altri tipi di collezioni, ma questi sono i principali in cui potete imbattervi. In realtà solo alcuni di questi saranno trattati in questo testo!

I File

Come utilizzatori di computer saprete tutto sui file, le fondamenta di quasi tutto ciò che ha a che fare con i computer. Non sarete sorpresi quindi nello scoprire che quasi tutti i linguaggi di programmazione forniscono uno speciale tipo di dato file. Comunque i file ed il loro trattamento è cosí importante che rimanderemo la discussione a più avanti, dedicandovi un intero capitolo.

Data e tempo

Spesso nella programmazione vengono definiti tipi specifici per data e tempo. In altri casi essi sono semplicemente rappresentati mediante numeri di grandi dimensioni (tipicamente il numero di secondi trascorsi da una particolare data ed ora). In altri casi è un tipo di dato conosciuto come tipo composito e trattato nel prossimo paragrafo. Questo solitamente rende più semplice estrarre il mese, il giorno, l'ora, ecc.

Tipi compositi definiti dal programmatore

Qualche volta i tipi descritti sopra sono insufficienti anche se combinati in collezioni. Qualche volta vogliamo raggruppare parecchi dati diversi e poi trattarli come un unico elemento. Un esempio può essere la descrizione di un indirizzo:
un numero civico, una via ed una città. Magari anche un codice postale.

La maggior parte dei linguaggi ci consentono di raggruppare tali informazioni in un record o una struttura.

In BASIC la definizione di un record si presenta cosí:

Type Indirizzo
     Civico AS INTEGER
     Via AS STRING * 20
     Citta AS STRING * 15
     CAP AS STRING * 7
End Type

Il numero dopo STRING indica semplicemente la lunghezza massima della stringa.

In Python è un po' differente:

>>> class Indirizzo:
...   def __init__(self, civ, via, citta, cap):
...     self.Civico = civ
...     self.Via = via
...     self.Citta = citta
...     self.CAP = cap
...

Ciò può sembrare piuttosto misterioso, ma non preoccupatevi, spiegheremo il significato di def __init__(...) e self nel capitolo sulla programmazione ad oggetti. Alcuni hanno trovato difficoltà nel provare questo esempio al prompt di Python. Alla fine del capitolo troverete un riquadro con maggiori dettagli ma, se volete, potete anche attendere che l'argomento sia trattato nel seguito di questo corso. Se volete provare a scriverlo in Python allora badate a mantenere esattamente l'indentazione delle righe come nell'esempio. Come vedremo in seguito Python è molto esigente sui livelli di indentazione.

La cosa principale che dovreste notare in tutto ciò è che abbiamo raccolto parecchi dati diversi in una singola struttura.

Accesso ai dati nei tipi compositi

Possiamo assegnare ad una variabile un tipo di dato composito, ma per accedere ai singoli campi del tipo dobbiamo usare qualche meccanismo speciale (definito dal linguaggio). Di solito si usa il punto.

Prendendo in esame il caso del tipo indirizzo definito sopra in BASIC abbiamo:

DIM Ind AS Indirizzo
Ind.Civico = 7
Ind.Via = "Via Roma"
Ind.Citta = "Vattelappesca"
Ind.CAP = "123 456"
PRINT Ind.Via," ",Ind.Civico

In Python, supponendo che abbiate gia predisposto la definizione di classe mostrata sopra:

Ind = Indirizzo(7,"Via Roma","Vattelappesca","123 456")
print Ind.Via, Ind.Civico

Questo crea un'istanza del nostro tipo Indirizzo e la assegna alla variabile Ind. Poi possiamo visualizzare i campi Via e Civico dell'istanza appena creata usando l'operatore punto. Naturalmente possono essere create molte variabili di tipo Indirizzo ciascuna con propri valori di numero civico, via, ecc.

Alla maniera di TCL

In Tcl la migliore approssimazione ai tipi compositi consiste nel memorizzare i campi in una lista. Dovete ricordare la sequenza dei campi in modo da poterli estrarre in seguito. Questo potrebbe essere un po' semplificato assegnando gli indici dei campi a variabili, in questo modo l'esempio potrebbe essere scritto:

set Civico 0
set Via 1
set Citta 2
set CAP 3
set Ind [list 7 "Via Roma" "Vattelappesca" "123 456"]
puts [format "%s %s" [lindex $Ind $Via] [lindex $Ind $Civico]]

Notate l'uso delle stringhe di formato Tcl ed i gruppi di parentesi "[]" annidate.

Operatori definiti dal programmatore

I tipi definiti dal programmatore in alcuni linguaggi possono avere anche operazioni definite dal programmatore. Questa è la base della programmazione ad oggetti. A questo argomento dedicheremo un intero capitolo in seguito, ma essenzialmente un oggetto è una collezione di dati con associate le operazioni su tali dati, il tutto impacchettato in un'unica struttura. Python usa gli oggetti molto frequentemente nelle sua libreria di moduli di base e ci consente anche di definire i nostri tipi di oggetti.

Le operazioni su oggetti vengono utilizzate allo stesso modo degli elementi di dato di un tipo definito dal programmatore, utilizzando l'operatore punto, ma per tutti gli altri aspetti si comportano come funzioni. Queste speciali funzioni vengono dette metodi. Abbiamo già visto un esempio con l'operazione append() di una lista. Ricordate che occorre etichettare la chiamata di funzione con il nome della variabile:

>>> unaLista = []        # una lista vuota
>>> unaLista.append(42)  # esecuzione di un metodo dell'oggetto lista
>>> print unaLista
[42]

Quando un tipo oggetto, detto classe, viene definito in un modulo, è necessario importare il modulo (come abbiamo fatto con sys qualche capitolo addietro) quindi premettere il tipo oggetto con il nome del modulo, per creare un'istanza che possiamo memorizzare in una variabile. Poi possiamo usare la variabile senza usare il nome del modulo.

Illustreremo questo punto considerando un ipotetico modulo carne che definisce una classe Prosciutto. Importiamo il modulo, creiamo una istanza della classe Prosciutto e utilizziamo le operazioni ed i dati come segue:

>>> import carne
>>> mioProsciutto = carne.Prosciutto()  # crea un'istanza usando il nome del modulo
>>> mioProsciutto.fetta()        # uso di una operazione di Prosciutto
>>> print mioProsciutto.ingredienti  # accesso ai dati di Prosciutto
{Maiale:40%, Prosciutto:45%, Grassi:15%}

Oltre alla necessità di creare un'istanza, non c'è differenza fra usare gli oggetti o le funzioni definiti all'interno di moduli. Potete pensare ad un nome di oggetto come un'etichetta che raggruppa variabli e funzioni che hanno una relazione fra loro.

Per esprimerlo in altri termini gli oggetti rappresentano oggetti del mondo reale su cui noi programmatori possiamo compiere azioni. Questa prospettiva spiega da dove è nato il concetto di oggetto nella programmazione: la scrittura di programmi per la simulazione di situazioni del mondo reale.

Né il QBASIC né il Tcl forniscono strumenti per associare operatori ai tipi compositi. Esistono però librerie aggiuntive per Tcl che lo consentono ed anche il dialetto più moderno di BASIC, Visual Basic, lo permette.

Operatori specifici di Python

Lo scopo principale di questo testo consiste nell'insegnare a programmare e, sebbene io usi Python nel testo, non c'è ragione per cui voi non possiate, dopo averlo letto, procurarvi un testo su un altro linguaggio ed usare quello. In effetti è proprio quello che mi aspetto che facciate, in quanto nessun linguaggio di programmazione, nemmeno Python, può fare tutto. Tuttavia a causa di questo mio scopo non tutte le caratteristiche di Python sono trattate, ma viene posto l'accento su quelle che possono essere trovate anche in altri linguaggi. Come conseguenza ci sono numerose caratteristiche di Python che, anche se assai potenti, non saranno affatto trattate, fra queste sono inclusi gli operatori speciali. Molti linguaggi di programmazione supportano operazioni che altri linguaggi non prevedono. Spesso sono questi operatori "unici" che determinano la nascita di un nuovo linguaggio e che certamente ne determinano la popolarità.

Ad esempio Python supporta operazioni relativamente poco comuni come l'accesso a sezioni di liste ( Prosciutto[X:Y] ) (in inglese: slicing, N.d.t.) e l'assegnazione di tuple ( X, Y = 12, 34 ). Ha anche la possibilità di effettuare un'operazione su tutti i membri di una lista usando la funzione map(). Ce ne sono ancora molte altre: spesso si dice che "Python viene fornito completo di batterie". Per maggiori dettagli sull'uso di questi costrutti specifici di Python dovrete consultare la documentazione relativa.

Per finire è opportuno sottolineare che, anche se abbiamo detto che queste operazioni sono specifiche di Python, non significa che non sono presenti in nessun altro linguaggio, ma piuttosto che non sono tutte disponibili in tutti i linguaggi di programmazione.

Questo conclude il nostro sguardo alle materie prime della programmazione, adesso passeremo all'argomento più interessante delle tecniche e vedremo come possiamo usare questi materiali.

Alcuni dettagli aggiuntivi sull'esempio dell'Indirizzo

Sebbene, come abbiamo detto sopra, i dettagli di questo esempio saranno spiegati in seguito, alcuni lettori hanno incontrato difficoltà nel far funzionare l'esempio. Questa nota fornisce una spiegazione riga per riga del codice Python:

Il codice completo dell'esempio è il seguente:


>>> class Indirizzo:
...   def __init__(self, cv, via, citta, cap):
...     self.Civico = cv
...     self.Via = via
...     self.Citta = citta
...     self.CAP = cap
...
>>> Ind = Indirizzo(7,"Via Roma","Vattelappesca","123 456")
>>> print Ind.Civico, Ind.Via

Ecco le spiegazioni dettagliate:

>>> class Indirizzo:

L'istruzione class indica a Python che vogliamo definire un nuovo tipo di dato chiamato, in questo caso, Indirizzo. I "due punti" indicano che ogni linea rientrante che segue l'istruzione fa parte della definizione della classe. La definizione termina con la prima riga non rientrante. Se state utilizzando IDLE vedrete che l'editore di testo vi propone la linea rientrante, se invece state usando il prompt dei comandi di Python in una finestra MS-DOS dovrete scrivere la riga indentata inserendo manualmente alcuni spazi come nell'esempio. Python non si cura di quanti spazi usate nel rientro purché siano sempre gli stessi.

...   def __init__(self, cv, via, citta, cap):

La prima istruzione nella nostra classe è ciò che viene chiamato una definizione di metodo. Questo metodo ha nome __init__ ed è un'operazione speciale effettuata da Python quando viene creata una nuova istanza della classe, la vedremo fra breve. I due punti, come prima, indicano semplicemente che il gruppo successivo di righe indentate costitusce la definizione del metodo.

...     self.Civico = cv

Questa riga e le tre successive assegnano valori ai campi interni del nostro oggetto. Sono indentate rispetto all'istruzione def per indicare a Python che si tratta della definizione dell'operazione __init__. La riga vuota indica all'interprete di Python che la definizione di classe è terminata, in tal modo otteniamo di nuovo il prompt >>>.

>>> Ind = Indirizzo(7,"Via Roma","Vattelappesca","123 456")

Questa istruzione crea una istanza del nostro tipo Indirizzo e Python usa l'operazione __init__ definita sopra per assegnare i valori che abbiamo specificato ai campi interni. L'istanza viene assegnata alla variabile Ind esattamente come potrebbe essere assegnata un'istanza di qualunque altro oggetto.

>>> print Ind.Civico, Ind.Via

Adesso visualizziamo i valori di due dei campi interni usando l'operatore punto per accedervi.

Come detto sopra tratteremo più in dettaglio tutto ciò più avanti. Il punto fondamentale da notare è che Python consente di creare i propri tipi di dato ed usarli in modo molto simile ai tipi fondamentali.


Promemoria
  • Ci sono molti tipi di dati e le operazioni che si possono applicare ad essi dipendono dal tipo di dato che stiamo usando.
  • I tipi primitivi includono le stringhe di caratteri, i numeri, i valori booleani o "logici".
  • I tipi di dato compositi includono le collezioni, i file, le date e quelli definiti dal programmatore.
  • In ogni linguaggio di programmazione si trovano molti operatori e fa parte dell'apprendimento del linguaggio acquisire familiarità con i tipi di dato e con le operazioni disponibili per tali tipi.
  • Lo stesso operatore (ed esempio l'addizione) può essere disponibile per tipi differenti, ma il risultato può non essere identico ed addirittura apparentemente non correlato!

Precedente  Successivo  Indice


Se avete domande o suggerimenti relativi a questa pagina mandate un e-mail all'autore: alan.gauld@yahoo.co.uk o al traduttore italiano: lfini@arcetri.astro.it