Python

Tipi: le stringhe              

Le stringhe sono un elemento fondamentale in tutti i linguaggi di programmazione; sono entità estremamenti duttili in grado sia di contenere normali sequenze rappresentative di una qualsiasi parola o una frase sia, ad esempio, una collezione di bytes rappresentativi di una qualche altra soggetto, come un'immagine. Normalmente comunque vale il primo esempio e così sarà nel prosieguo di questo paragrafo. Le stringhe fanno parte di una famiglia più ampia che sono le sequenze con le quali si intende una successione di oggetti in senso più generico e delle quali vedremo altri esempi. In quanto sequenze le stringhe sono ordinate (da sinistra a destra, da un punto di vista logico) ed indicizzate, ovvero ogni elemento costitutivo ha un suo indice univoco che lo individua all'interno della stringa. C'è stata una grossa evoluzione passando da Python 2.x a 3.x, sostanzialmente la cosa più importante è che nella versione più vecchia avevamo distinzione tra stringhe Unicode e stringhe non Unicode, Python 3 considera solo stringhe Unicode. Noi, come detto, ci occupiamo della versione più alta. Le stringhe in Python sono immutabili, quindi ogni tentativo di modifica in realtà ne crea una nuova contenente le variazioni applicate a quella di partenza.
Da un punto di vista formale esse sono delimitate da una coppia di doppi o singoli apici, per cui gli esempi sono banali:

"ciao"
"W la Juve"
"1 + 2 = 3"
"hello, World"
'aabbcc'
'hallo Welt'

Il terzo esempio è una stringa in forza della della presenza della coppia di doppi apici. Non c'è sostanziale differenza nell'uso delle stringhe in nei due formati, ovvero singoli o doppi apici; si tratta semplicemente di una preferenza stilistica. Il fatto di supportare entrambi i formati rende più semplice usare l'altro simbolo all'interno di una stringa, quindi si possono usare i doppi apici all'interno di una stringa racchiusa tra due apici singoli e viceversa. Il formato più diffuso nel mondo Python è quello che prevede i singoli apici (i programmatori che vengono dal mondo C/C++ non saranno troppo contenti)
Python ammette anche una forma in cui la stringa è racchiusa all'interno di due terne di doppi o singoli apici:

"""ciao"""
'''ciao'''

anche in questo caso usare doppi apici o singoli apici è una questione di gusti, o meglio di opportunità. Le stringhe con quest'ultimo formato sono usate spesso quando il testo è molto lungo e deve essere spezzato con frequenti "a capo" che in questo caso non è necessario introdurre con la caratteristica sequenza di escape e può contenere a sua volta anche apici e doppi apici, anche in questo caso senza ricorrere alle sequenze di escape. Una pratica a mio avviso poco simpatica è quella di usare un blocco compreso in una coppia di tripli apici per disabilitare temporaneamente una porzione di codice, che a quel punto sarebbe solo una stringa non utilizzata. Lo segnalo solo perchè vi potrebbe capitare. A me questa pratica non piace.
Prima di passare ad un esempio vediamole quindi queste sequenze di escape esplicitandone l'uso:

Escape Uso
\newline ignora newline
\\ backslash
\' singolo apice
\" doppio apice
\a suono
\b backspace
\f formfeed
\n linefeed - a capo
\N{nome} carattere Unicode con un dato nome
\ooo Carattere con un dato valore ottale
\r ritorno del carrello
\t tabulazione orizzontale
\uhhhh carattere Unicode con un dato valore esadecimale a 16 bit
\uhhhhhhhh carattere Unicode con un dato valore esadecimale a 32 bit
\v tabulazione verticale
\xhh carattere Unicode con un dato valore esadecimale a 8 bit

Non è detto che nella vita professionale li userete tutti, alla fin fine.
Ed eccoci quindi ad un esempio:

  Esempio 3.1
1
2
3
4
5
6
7
8
9
10
11
print("Questa è una stringa")
print('Anche questa è una stringa')
print("""Questa è un'altra stringa""")
print()
print("""Questa invece è
una stringa che si espande
su più righe""")
print()
print("stringa\nspezzata")
print()
print('Stringa \t con tabulatore')

con il seguente output

Questa è una stringa
Anche questa è una stringa
Questa è un'altra stringa

Questa invece è
una stringa che si espande
su più righe

stringa
spezzata

Stringa con tabulatore

Come vedete nulla di eclatante ne di particolarmente differente da quanto visto in altri linguaggi. Alle righe 4, 8 e 10, abbiamo delle stringhe vuote, praticamente print si limita ad andare a capo. Ovviamente dovete ricordare di usare per la chiusura della stringa lo stesso tipo di apice (doppio, singolo, triplo doppio, triplo singolo) che avete usato per aprirle. Non sono ammesse commistioni. Tra l'altro a volte è necessario, come classico esempio quando indichiamo un path sui dischi fissi, usare delle sequenze che richiamano quelle di escape ma non devono essere usate come tali.  Questo problema può essere risolto in due modi:

>>> s1 = r'c\n\t\v'
>>> s2 = 'c\\n\\t\\v'
>>> print(s1)
c\n\t\v
>>> print(s2)
c\n\t\v

La prima riga ci presenza una raw string, introdotta da quella "r" (si può anche usare la forma maiuscola) che vediamo prima dell'apice introduttivo. Una stringa "cruda" che non fa altro che conservare i caratteri esattamente così essi sono proposti nella stringa. La seconda riga è invece un metodo più immediato anche se forse meno elegante e universale, per raggiungere lo stesso scopo. E' interessante notare che la funzione len, funzione che spiegheremo tra poco, restituisce lo stesso risultato per s1 e per s2. Una raw string non può terminare con un numero di dispari di backslash. Quindi r'aaa\' non è accettabile, se proprio è necessario che la vostra stringa termini in quel modo potete usare la seconda forma oppure unire raw string con una formata dal carattere "\", vedremo più avanti come si uniscono due o più stringhe per formarne una sola.

Da un punto di vista espressivo è molto comodo poter interpolare variabili all'interno di una stringa. Vediamo come si fa:

 >>> x1 = 1
>>> x2 = 2
>>> x3 = x1 + x2
>>> print('{x1} + {x2} = {x3}'.format(**vars()))
1 + 2 = 3

Quindi il "trucco" sta nel mettere il nome della variabile semplicemente all'interno di una coppia di parentesi graffe e poi far seguire da quel codice un po' strano evidenziato in rosso. Questo deriva dal fatto che Python non supporta direttamente l'interpolazione e quindi, tra le varie strade, possiamo ricorrere a format, di cui parleremo in una apposita sezione. Esistono comunque altre soluzioni ma quella qui esposta è senza dubbio la più semplice da mettere in pratica. Per ora prendetela così com'è. Un'altra via, più semplice ma che per poter essere utilizzata necessita che conosciate a priori la natura delle variabili che vorrete interpolare all'interna della stringa, è la seguente:

>>> s1 = "Gianni"
>>> n1 = 18
>>> print('%s ha %d anni' %(s1, n1))
Gianni ha 18 anni

quindi abbiamo definito le variabili e le abbiamo inserite in via parametrica, non direttamente quindi, all'interno della scritta tramite un descrittore che ne delimita la natura. Esiste una tabella specifica per tali descrittori:

Parametro Descrizione
%s stringa
%c carattere singolo
%d numero decimale (anche intero)
%u intero senza segno
%o numero in notazione ottale
%x numero in notazione esadecimale
%g numero reale in notazione normale
%e numero reale in notazione scientifica
%ld long
%lu long senza segno
&lld long long
%llu long long senza segno
 %i intero
&p void

Come abbiamo detto una stringa è composta di altre parti più piccole che sono i singoli caratteri. Questi, abbiamo anche accennato, sono indicizzati attraverso sequenze continue di  numeri interi. Ogni singolo elemento è identificato appunto tramite un intero all'interno dell'operatore []. Le stringhe sono 0-based, ovvero il primo indice è 0 l'ultimo è n-1 dove n è il numero di elementi, che compongono la stringa. Nel selezionare gli indici bisogna stare dentro i limiti. Quindi:

>>> a = "abcdefgh"
>>> print(a[0])
a
>>> print(a[1],a[3])
b d
>>> print(a[9])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: string index out of range
>>>

Ecco qua, l'ultima istruzione crea un errore perchè a[9] non può esistere per quanto detto, 9 è fuori range avendo la stringa 8 caratteri e quindi i suoi indici sono compresi nell'intervallo 0 - 8.
La prima riga ci spiega come costruire dichiarare una stringa; si tratta del metodo più facile e veloce, lo troverete molto spesso leggendo codice Python. In pratica, è una semplice assegnazione di una stringa, definita in uno dei modi che abbiamo visto, ad una variabile. Esiste anche la utilissima funzione str che aiuta la conversione di un oggetto, teoricamente di qualsiasi natura, in formato stringa:

>>> str(150)
'150'
>>> str(1.56)
'1.56'
>>> a = 890
>>> str(a)
'890'
>>>

str verrà comoda in molte occasioni.

Per quanto non comune è permesso in questo linguaggio anche l'uso di indici negativi o meglio è permesso in un certo senso guardare la stringa anche da destra verso sinistra usando indici negativi. La figura seguente, con l'esempio pratico subito dopo, chiarisce questo concetto che potrà essere utilizzato, come vedremo più avanti, nei range:

indici normali 0 1 2 3
stringa a b c d
indici negativi -4 -3 -2 -1

>>> aa = "abcd"
>>> print(aa[-1])
d
>>> print(aa[-4])
a

Vediamo ora alcune delle operazioni più comuni con le stringhe insieme con le proprietà più utili.
Una delle prime cose che spesso usiamo manipolando le stringhe è la loro lunghezza, ovvero il numero di elementi che la compongono. Si usa un metodo generico e valido per tutte le liste di elementi, ne vedremo altre, ovvero len() della cui esistenza avevamo accennato in precedenza:

>>> s1 = "ciao"
>>> print(len(s1))
4

>>> print(len('abcd'))
4

Oltre al + Python ci permette di usare anche il * sulle stringhe, col risultato di creare una stringa frutto dell'iterazione di quella di partenza. In alcuni casi può essere molto utile:

>>> s = 'a'
>>> print(s * 10)
aaaaaaaaaa

Operazione molto comune è poi la concatenazione per ottenere la quale esistono varie strade, alcune delle quali piuttosto elaborate le potreme vedere più avanti. Un primo modo, semplicissimo, è quello di usare un overload dell'operatore +:

>>> s1 = 'aa'
>>> s2 = 'bb'
>>> s3 = s1 + s2
>>> print(s3)
aabb

Direi quasi banale. Tale sistema è molto comodo ma la sua efficienza in presenza di un numero molto elevato di concatenazioni non è gran che.
Un secondo sistema fa uso del metodo join(). Essa si preoccupa di creare una stringa partendo da una sequenza; tale sequenza è l'unico argomento che accetta mentre è previsto un separatore. La cosa può non essere chiarissima vediamo quindi la sitassi generica di join:

str.join(sequenza)

 str è la stringa usata come separatore mentre la sequenza è una ennupla costitutita dalle stringhe che si vuole concatenare. Vediamo l'esempio:

>>> str = ':'
>>> sequenza = ('aa', 'bb', 'cc')
>>> s4 = str.join(sequenza)
>>> print(s4)
aa:bb:cc

La terza riga riporta la nostra definizione messa in pratica. Ovviamente, se non volete alcun separatore, si può scrivere
str = ''  ponendo una stringa vuota come separatore o anche, similmente,
s4 = ''.join(seq)

Questo sistema di concatenazione è significativamente più performante di quello che usa il +.

Il terzo sistema, in qualche modo simile al precedente, è il più efficente in quasi tutte le situazione e fa uso della list-comprehension, argomento abbastanza specifico e non presente in tutti i linguaggi,di cui parleremo altrove. Per ora basti solo il seguente esempio:

>>> seqq = ("aa", "bb", "cc", "dd", "ee")
>>> s4 = ''.join([x for x in seqq])
>>> print(s4)
aabbccddee

Molto comoda è la possibilità di lavorare, anzichè sul singolo elemento di una stringa, su una certa porzione di essa; ciò avviene tramite dei range, definiti formalmente come segue:

[inizio range : fine range]

come vedremo entrambi i parametri sono opzionali

>>> s1 = 'abcdefgh'
>>> s2 = s1[2:5]
>>> print(s2)
cde

L'esempio mostra che in realtà gli elementi presi in considerazione vanno da 2 a 4 mentre l'elemento all'indice di fine range non è compreso

s1 a b c d e f g h
indici 0 1 2 3 4 5 6 7
s2 c d e          

infatti per comprendere nel range l'ultimo indice bisogna spostarci avanti di una posizione ad esempio:

>>> s3 = s1[2:8]
>>> print(s3)

cdefgh

ci sono comunque varie possiblità, vediamo qualche esempio:

>>> s4 = s1[:5]
>>> print(s4)
abcde
>>> s5 = s1[2:]
>>> print(s5)
cdefgh
>>> s6 = s1[:]
>>> print(s6)
abcdefgh

In alcuni casi questo sistema di selezione viene molto comodo. Come abbiamo accennato in precedenza è possibile usare anche indici negativi per selezionare gli elementi di una stringa e possiamo anche utilizzarli all'interno dei range.

>>> s0 = 'abcdefgh
>>> s1 = s0[:-1]
>>> print(s1)
abcdefg
>>> s2 = s0[-4:-2]
>>> print(s2)
ef
>>> s3 = s0[2:-2]
>>> print(s3)
cdef

Ovviamente a questo punto lascio libero spazio ai vostri esperimenti. Tenete presente che i range funzionano come strumento di selezione da sinistra verso destra e non viceversa se usati con i più consueti numeri positivi; come avrete capito per andare nell'altro senso userete invece i numeri negativi.
Rimanendo su questo argomento esiste anche un metodo di selezione basato sui range un poco più sofisticato in quanto vi permette di specificare un passo di selezione (e la cosa sarà utile as esempio quando parleremo delle istruzioni di controllo). Vediamo la sintassi e un esempio:

[inizio range : fine range : passo]

>>> s0
'abcdefgh'
>>> s4 = s0[0:7:2]
>>> print(s4)
aceg

Sono stati presi in considerazione gli elementi aventi indice 0 - 2 - 4 - 7. Ovviamente in questi casi è d'obbligo specificare inizio e fine range, sembrerebbe. Invece no:

>>> s6 = s0[::3]
>>> print(s6)
adg
>>> s7 = s0[3::2]
>>> print(s7)
dfh
>>> s8 = s0[:7:3]
>>> print(s8)
adg
>>>

La cosa funziona anche con i  numeri negativi, come anticipato, utili per andare da destra verso sinistra:

>>> s9 = s0[-1:-8:-2]
>>> print(s9)
hfdb

E ora divertitevi con le vostre prove.

Cercare elementi in una stringa è molto facile grazie al metodo find(). La sua sintassi è la seguente:

stringa.find(stringa-da-cercare [, indice di partenza (default = 0), indice finale (default = indice massimo della stringa)])

Ciò che sta all'interno della parentesi quadrata è opzionale. Il metodo restituisce l'indice di partenza della prima occorrenza della sottostringa, se trovata, altrimenti il risultato è -1. In questo senso è un sistema per verificare se una certa stringa è presente come sottostringa.

>>> st = "aabbccddeeaa"
>>> st.find('a')
0
>>> st.find('bb')
2
>>> st.find('a',1)
1
>>> st.find('k')
-1
>>> st.find('bbc')
2
>>> st.find('a',2, 11)
10

La prima istruzione trova la prima occorrenza di 'a' all'indice 0
La seconda trova l'occorrenza di 'bb' che inizia appunto all'indice 2
La terza trova 'a' a partire dall'indice 1 e la troviamo proprio all'indice 1 (la 'a' all'indice 0 è tagliata fuori)
La quarta non trova 'k' e ci restituisce -1
La quinta trova la stringa 'bbc' ad iniziare dall'indice 2
La sesta infine cerca 'a' partendo dall'indice 2 e arrivando fino a 11 e la trova all'indice 10.

Potete fare altre prove con stringhe più complesse.

Dopo la ricerca abbiamo un'altra operazione classica, la sostituzione di una parte della stringa. Il primo metodo utile in questo caso è replace. L'istruzione è molto semplice nella sintassi:

replace(stringa da sostituire, nuova stringa)
esempio:

>>> sr0 = 'abacadae'
>>> print(sr0.replace('a','zz'))
zzbzzczzdzze 
>>> print(sr0.replace('a',''))
bcde

Nel secondo caso abbiamo usato una stringa vuota e quindi la 'a' scompare del tutto. Il metodo replace è un sistema di base per la sostituzione nell'ambito delle stringhe. Più avanti incontreremo altre possibilità più evolute e potenti ma come base replace è più che sufficiente, anche perchè ha il vantaggio dell'immediatezza e della semplicità.

Tra le domande più frequenti relativamente alle stringhe c'è anche quella del passaggio da numero a stringa e viceversa. Il problema è di facile soluzione grazie alle funzioni str, già vista, int e float. Il loro uso è banale e lo chiariremo con l'esempio seguente:

>>> s1 = "132"
>>> i1 = int(s1)
>>> print(i1 + 1)
133
>>> s2 = str(i1)
>>> print(s2 + " a")
132 a
>>> s3 = "1.234"
>>> f1 = float(s3)
>>> print(f1 + 1)
2.234
>>>

Come è logico non si possono sommare o concatenare stringhe e numeri senza prima aver compiuto le opportune conversioni; insomma scrivere

"pippo" + 1 non si può

Correlato al problema appena affrontato è quello di dover passare da intero a carattere Unicode corrispondente e viceversa. Anche qui abbiamo un paio di semplici funzioni che ci evitano qualunque mal di testa: ord e chr. La prima ci fornisce l'intero che corrisponde ad un certo carattere e la seconda fa il contrario.

>>> print(ord('a'))
97
>>> print(chr(97))
a

Talvolta può essere utile verificare a priori la natura di una stringa prima di procedere al suo utilizzo. Molti linguaggi offrono strumenti dedicati al problema e Python non fa eccezione. I metodi più frequentemente usati per questa finalità sono certamente isdigit e isalpha. Il primo verifica che la stringa sia di natura numerica, la seconda che tutti i caratteri sia effettivamente lettere; in entrambi i casi la lunghezza minima è uno, cioè ci deve essere almeno un elemento. I due metodi citati restituiscono valori booleani, ovvero true o false. e il loro uso è banale.

>>> s1 = 'abcd'
>>> s2 = '1234'
>>> s3 = 'ab12'
>>> s4 = ''
>>> s1.isalpha()
True
>>> s2.isdigit()
True
>>> s3.isalpha()
False
>>> s3.isdigit()
False
>>> s4.isalpha()
False
>>> s4.isdigit()
False
>>>

Un ulteriore interessante accenno è la possibilità, già da Python 2.0 è la possibilità di definire direttamente stringhe Unicode. Queste hanno il vantaggio di poter garantire una conversione automatica di caratteri particolari dove necessario e di fornire un ordinale per ogni specifico carattere, superando le limitazioni della codifica ASCII. Per informare l'interprete che vogliamo utilizzare una stringa Unicode è sufficiente usare il prefisso u prima della stringa (senza spazi di separazione) come nel seguente esempio (ovviamente le stringhe, come consuetudine, possono essere comprese tra una coppia di singoli o doppi apici):

'ciao mondo'
>>> x = u'ciao mondo'
>>> print(x)
ciao mondo

Come detto viene garantita l'automatica conversione di qualsiasi carattere Unicode inserito nella stringa offrendoci una grande capacità espressiva per le nostre stringhe:

>>> s1 = u"ciao\u0020mondo"
>>> s2 = u'ciao\u0022mondo'
>>> print(s1)
ciao mondo
>>> print(s2)
ciao"mondo

Molto semplice ed immediato.
In passato era presente anche il supporto per una modalità Raw-Unicode, introdotta dal prefisso ur che ora è stato inglobato dal prefisso r.

Prima di chiudere vediamo una rapida panoramica di alcuni metodi built-in, oltre a quelli già visti in precedenza, che vi potranno essere utili nell'uso delle stringhe; ovviamente sul sito ufficiale (consigliato) ed anche altrove (conrollate lo stato di aggiornamento) è possibile reperire un elenco completo dei metodi a disposizione:

Metodo Descrizione Esempio



capitalize Restituisce la stringa solo primo carattere maiuscolo >>> s1 = "abcd"
>>> print(s1.capitalize())
Abcd



count Restituisce il numero di occorrenze di una certa sottostringa. Opzionalmente è possibile definire un indice di partenza ed uno finale quindi il formato è triplice:
str.count(sub)
str.count(sub,start)
str.count(sub,start,end)
>>> s2 = 'abcdabcd'
>>> print(s2.count('a'))
2
>>> print(s2.count('a',3))
1
>>>



lower Restituisce la stringa con i caratteri maiuscoli eventualmente presenti convertiti in minuscolo >>> s3 = 'aBCDeF'
>>> print(s3.lower())
abcdef



split Restituisce una lista in cui ogni elemento è una singola parola, separata quindi da almeno uno spazio dalla precedente e dalla successiva. E' possibile specificare un separatore custom e l'indice massimo possibile nella lista. Quindi la sintassi è:
str.split()
str.split('separatore')
str.split('separaratore', indice)
>>> s4 = 'ciao a tutto il mondo'
>>> print(s4.split())
['ciao', 'a', 'tutto', 'il', 'mondo']
>>> print(s4.split(' ', 2))
['ciao', 'a', 'tutto il mondo']
>>> print(s4.split(' ', 0))
['ciao a tutto il mondo']
>>> print(s4.split(' ', 1))
['ciao', 'a tutto il mondo']



strip elimina i caratteri specificati (di default è il blank) a destra ed a sinistra della stringa. Esistino anche i metodi rstrip ed lstrip che lavorano rispettivamente solo a destra o a sinistra della stringa. Sintassi:
str.split()
str.split(carattere)
>>> s1 = ' abcd '
>>> print(s1.strip() + 'xxxx')
abcdxxxx
>>> s2 = 'xxxabcdxxx'
>>> print(s2.strip('x'))
abcd



swapcase scambia maiuscole con minuscole e viceversa >>> s1 = 'aBcDeF'
>>> print(s1.swapcase())
AbCdEf



upper Restituisce la stringa con gli eventuali caratteri minuscoli presenti convertiti in maiuscolo >>> s1 = 'abcd'
>>> print(s1.upper())
ABCD

Un capitolo piuttosto importante non può non essere dedicato alla formattazione delle stringhe. E' questa una pratica molto usata e utile a livello di presentazione del testo. La cosa è molto interessante e non semplicissima di primo acchito (dopo un po' di pratica diventerà quasi banale) e, come già detto, ce ne occuperemo in apposito paragrafo.