Finora abbiamo visto tre tipi di dati: int, float e string. Le stringhe sono qualitativamente diverse dagli altri tipi di dati poiché sono composte di unità più piccole: i caratteri.
I tipi di dati che sono fatti di elementi più piccoli sono detti tipi di dati composti. A seconda di ciò che stiamo facendo possiamo avere la necessità di trattare un tipo composto come fosse una singola entità o possiamo voler agire sulle sue singole parti. Questa duplice possibilità è molto utile.
L'operatore porzione seleziona dei caratteri da una stringa:
>>> Frutto = "banana"
>>> Lettera = Frutto[1]
>>> print Lettera
L'espressione Frutto[1] seleziona il carattere numero 1 dalla stringa Frutto. La variabile Lettera contiene il risultato. Quando stampiamo Lettera abbiamo però una sorpresa:
a
La prima lettera di "banana" logicamente non è "a": in informatica i conteggi partono spesso da 0 e non da 1 come potrebbe sembrare normale e per accedere al primo carattere di una stringa dobbiamo quindi richiedere il numero 0, per il secondo il numero 1 e così via. Sembra un po' illogico ma ci farai facilmente l'abitudine perché questo è il modo normale di contare in molti linguaggi di programmazione. Quindi se vogliamo sapere l'iniziale della stringa scriviamo:
>>> Lettera = Frutto[0]
>>> print Lettera
b
L'espressione tra parentesi quadrate è chiamata indice. Un indice identifica un particolare elemento di un insieme ordinato che nel nostro caso è l'insieme dei caratteri di una stringa. L'indice può essere una qualsiasi espressione intera.
La funzione len ritorna il numero di caratteri di una stringa:
>>> Frutto = "banana"
>>> len(Frutto)
6
Per ottenere l'ultimo carattere di una stringa potresti essere tentato di fare qualcosa di simile a:
Lunghezza = len(Frutto)
Ultimo = Frutto[Lunghezza] # ERRORE!
ma c'è qualcosa che non va: infatti ottieni un errore IndexError: string index out of range dato che stai facendo riferimento all'indice 6 quando quelli validi vanno da 0 a 5. Per ottenere l'ultimo carattere dovrai quindi scrivere:
Lunghezza = len(Frutto)
Ultimo = Frutto[Lunghezza-1]
In alternativa possiamo usare indici negativi che in casi come questo sono più comodi, contando a partire dalla fine della stringa: l'espressione Frutto[-1] ritorna l'ultimo carattere della stringa, Frutto[-2] il penultimo e così via.
Molti tipi di elaborazione comportano un'azione su una stringa un carattere per volta. Spesso queste elaborazioni iniziano dal primo carattere, selezionano un carattere per volta e continuano fino al completamento della stringa. Questo tipo di elaborazione è definita elaborazione trasversale o attraversamento, in quanto attraversa la stringa dall'inizio alla fine. Un modo per implementare un'elaborazione trasversale è quello di usare un ciclo while:
Indice = 0
while Indice < len(Frutto):
Lettera = Frutto[Indice]
print Lettera
Indice = Indice + 1
Questo ciclo attraversa la stringa e ne mostra una lettera alla volta, una per riga. La condizione del ciclo è Indice < len(Frutto) così che quando Indice è uguale alla lunghezza della stringa la condizione diventa falsa, il corpo del ciclo non è eseguito ed il ciclo termina. L'ultimo carattere cui si accede è quello con indice len(Frutto)-1 che è l'ultimo carattere della stringa.
Esercizio: scrivi una funzione che prende una stringa come argomento e la stampa un carattere per riga partendo dall'ultimo carattere.
Usare un indice per attraversare un insieme di valori è un'operazione così comune che Python fornisce una sintassi ancora più semplice: il ciclo for.
for Lettera in Frutto:
print Lettera
Ad ogni ciclo, Lettera assume il valore del prossimo carattere della stringa Frutto, così che Frutto viene attraversata completamente finché non rimangono più caratteri da analizzare.
L'esempio seguente mostra come usare il concatenamento e un ciclo for per generare una serie alfabetica, e cioè una lista di valori nei quali gli elementi appaiono in ordine alfabetico. Per esempio nel libro Make Way for Ducklings di Robert McCloskey i nomi dei protagonisti sono Jack, Kack, Lack, Mack, Nack, Ouack, Pack e Quack. Questo ciclo restituisce i nomi in ordine:
Prefissi = "JKLMNOPQ"
Suffisso = "ack"
for Lettera in Prefissi:
print Lettera + Suffisso
Il risultato del programma è:
Jack
Kack
Lack
Mack
Nack
Oack
Pack
Qack
Non è del tutto corretto dato che Ouack e Quack sono scritti in modo errato.
Esercizio: modifica il programma per correggere questo errore.
Un segmento di stringa è chiamato porzione. La selezione di una porzione è simile alla selezione di un carattere:
>>> s = "Pietro, Paolo e Maria"
>>> print s[0:6]
Pietro
>>> print s[8:13]
Paolo
>>> print s[16:21]
Maria
L'operatore [n:m] ritorna la stringa a partire dall' "n-esimo" carattere incluso fino all' "m-esimo" escluso. Questo comportamento non è intuitivo, e per comprenderlo è meglio immaginare i puntatori tra i caratteri, come nel diagramma seguente:
Se non è specificato il primo indice (prima dei due punti :) la porzione parte dall'inizio della stringa. Senza il secondo indice la porzione finisce con il termine della stringa:
>>> Frutto = "banana"
>>> Frutto[:3]
'ban'
>>> Frutto[3:]
'ana'
Secondo te cosa significa Frutto[:]?
Gli operatori di confronto operano anche sulle stringhe. Per vedere se due stringhe sono uguali:
if Parola == "BANANA":
print "stai parlando di un frutto!"
Altri operatori di confronto sono utili per mettere le parole in ordine alfabetico:
if Parola < "BANANA":
print "la tua parola" + Parola + "viene prima di BANANA."
elif Parola > "BANANA":
print "la tua parola" + Parola + "viene dopo BANANA."
else:
print "hai inserito la parola BANANA"
Devi comunque fare attenzione al fatto che Python non gestisce le parole maiuscole e minuscole come facciamo noi in modo intuitivo: in un confronto le lettere maiuscole vengono sempre prima delle minuscole, così che:
"BANANA" < "BAnana" < "Banana" < "bANANA" < "banana"
"ZEBRA" < "banana"
Un modo pratico per aggirare il problema è quello di convertire le stringhe ad un formato standard (tutto maiuscole o tutto minuscole) prima di effettuare il confronto.
Si può essere tentati di usare l'operatore porzione [] alla sinistra di un'assegnazione, con l'intenzione di cambiare un carattere di una stringa:
Saluto = "Ciao!"
Saluto[0] = 'M' # ERRORE!
print Saluto
Invece di ottenere Miao! questo codice stampa il messaggio d'errore TypeError: object doesn't support item assignment.
Le stringhe sono infatti immutabili e ciò significa che non puoi cambiare una stringa esistente. L'unica cosa che puoi eventualmente fare è creare una nuova stringa come variante di quella originale:
Saluto = "Ciao!"
NuovoSaluto = 'M' + Saluto[1:]
print NuovoSaluto
Abbiamo concatenato la nuova prima lettera ad una porzione di Saluto, e questa operazione non ha avuto alcun effetto sulla stringa originale.
Secondo te cosa fa questa funzione?
def Trova(Stringa, Carattere):
Indice = 0
while Indice < len(Stringa):
if Stringa[Indice] == Carattere:
return Indice
Indice = Indice + 1
return -1
In un certo senso questa funzione Trova è l'opposto dell'operatore porzione []: invece di prendere un indice e trovare il carattere corrispondente cerca in una stringa la posizione dove appare un carattere e ne restituisce l'indice. Se il carattere non è presente la funzione restituisce -1.
Questo è il primo esempio di return all'interno di un ciclo. Se Stringa[Indice] == Carattere il ciclo viene interrotto prematuramente. Se il carattere non fa parte della stringa il programma termina normalmente e ritorna -1.
Esercizio: modifica la funzione Trova per accettare un terzo parametro che rappresenta la posizione dove si deve cominciare a cercare all'interno della stringa.
Questo programma conta il numero di volte in cui la lettera 'a' compare in una stringa, usando un contatore:
Frutto = "banana"
Conteggio = 0
for Carattere in Frutto:
if Carattere == 'a':
Conteggio = Conteggio + 1
print Conteggio
La variabile Conteggio è inizializzata a 0 e poi incrementata ogni volta che è trovata una 'a' (incrementare significa aumentare di 1; è l'opposto di decrementare). Al termine del ciclo Conteggio contiene il risultato e cioè il numero totale di lettere a nella stringa.
Esercizio: incapsula questo codice in una funzione ContaLettera e fai in modo che questa accetti sia la stringa che la lettera da cercare come parametri.
Esercizio: riscrivi la funzione ContaLettera in modo che invece di elaborare completamente la stringa faccia uso della versione a tre parametri di Trova.
Il modulo string contiene funzioni molto utili per la manipolazione delle stringhe. Come abbiamo già visto prima di poter usare un modulo lo dobbiamo importare:
>>> import string
>>> Frutto = "banana"
>>> Posizione = string.find(Frutto, "a")
>>> print Posizione
1
In realtà string.find è più generale della nostra Trova. In primo luogo possiamo usarla per cercare stringhe e non soltanto caratteri:
>>> string.find("banana", "na")
2
Inoltre ammette un argomento ulteriore per specificare da dove vogliamo iniziare la nostra ricerca:
>>> string.find("banana", "na", 3)
4
Ancora, può prendere due argomenti che specificano il dominio di ricerca, cioè la porzione di stringa originale dove vogliamo effettuare la ricerca:
>>> string.find("bob", "b", 1, 2)
-1
In questo esempio la ricerca fallisce perché la lettera 'b' non appare nel dominio definito dagli indici 1 e 2 (da 1 incluso fino a 2 escluso).
È spesso necessario esaminare un carattere e controllare se questo è maiuscolo, minuscolo, o se si tratta di una cifra o di uno spazio bianco. A questo scopo il modulo string fornisce parecchie costanti molto utili.
La stringa string.lowercase contiene tutti i caratteri che il sistema considera minuscoli. Allo stesso modo string.uppercase contiene tutti i caratteri maiuscoli. Guarda cosa contengono queste stringhe:
>>> print string.lowercase
>>> print string.uppercase
>>> print string.digits
Possiamo usare queste costanti e la funzione find per classificare i caratteri. Per esempio se find(string.lowercase, Carattere) ritorna un valore diverso da -1 allora Carattere è minuscolo (un valore diverso da -1 indicherebbe infatti la posizione del carattere trovato):
def Minuscolo(Carattere):
return string.find(string.lowercase, Carattere) != -1
In alternativa possiamo usare l'operatore in che determina se un carattere compare in una stringa:
def Minuscolo(Carattere):
return Carattere in string.lowercase
o il consueto operatore di confronto:
def Minuscolo(Carattere):
return 'a' <= Carattere <= 'z'
Se Carattere è compreso tra 'a' e 'z' deve per forza trattarsi di una lettera minuscola.
Esercizio: prova a determinare quale di queste versioni è la più veloce. Puoi pensare ad altre ragioni, a parte la velocità, per preferire una versione piuttosto che un'altra?
Un'altra costante definita nel modulo string può sorprenderti quando provi a stamparla:
>>> print string.whitespace
I caratteri spazi bianchi infatti muovono il cursore senza stampare nulla: sono questi che creano lo spazio bianco tra i caratteri visibili. La costante string.whitespace contiene tutti gli spazi bianchi inclusi lo spazio, la tabulazione (\t) ed il ritorno a capo (\n).
Ci sono molte altre utili funzioni nel modulo string ma questo libro non è inteso per essere un manuale di riferimento come invece lo è la Python Library Reference, disponibile al sito ufficiale del linguaggio Python www.python.org.