Le tuple sono molto simili alle liste ma, a differenza di queste ultime NON SONO MODIFICABILI IN-PLACE (sono non-mutable).
Ha senso utilizzare le tuple quando vogliamo essere sicuri che il contenuto di una sequenza non vari durante la sua esistenza!
#definisco una tupla usando le parentesi tonde
#NB: elementi eterogenei
t1 = (1,'ciao',65.23,[1,2,3])
le parentesi tonde sono opzionali, una serie di elementi separati da virgola e' interpretata come tupla
#posso definire una toupla anche senza le parentesi tonde,
#specificandone gli elementi separati da virgola
t2 = 1,'ciao',65.23,[1,2,3]
print(t1)
uso gli operatori + e *
t2 = t1*3
print(t2)
Posso anche utilizzare gli slice per estrarre pezzi di tupla (vedi sotto *)
#stampo il secondo elemento
print(t1[1])
#stampo il secondo ed il terzo elemento, estratti con uno slice
print(t1[1:3])
lo slice ci restituisce una tupla!
(*) Di conseguenza posso usare lo slicing e gli operatori + e * per creare altre tuple:
t2[0:3]+2*t1[0:1] #OK
t2[0:3]+2*t1[0] #KO
#itero sulla toupla
for elem in t1:
print(elem)
Si è gia' detto che non posso assegnare un nuovo valore ad un elemento della toupla
t1 = (1,'ciao',65.23,[1,2,3])
#nel caso in cui l'elemento in questione è non mutable (t1[0], int)
#t1[0] = 0 # ERRORE
#ma anche quando e' mutable (t1[3], list)
#t1[3] = [] # ERRORE
tuttavia posso modificare 'in-place' un elemento mutable contenuto in una tupla:
print(t1)
idtuplaold = id(t1)
idlistaold = id(t1[3])
#NB: il quarto elemento della toupla e' una lista e quindi puo' essere modificato!!
t1[3][1]=10*t1[3][1]
print(t1)
idtuplanew=id(t1)
idlistanew=id(t1[3])
print(idtuplaold==idtuplanew)
print(idlistaold==idlistanew)
non solo e' possibile modificare gli elementi della lista ma e' anche possibile variare il numero di elementi, purche' le modifiche avvengano in-place
#NB anche se t1[3]=[] non e' permesso, questo invece lo posso fare!!!
del(t1[3][:])
print(t1)
t1[3].append(1)
print(t1)
Le stringhe possono essere pensate come delle touple di soli caratteri. Quindi:
Si puo' definire una nuova stringa con singoli apici ' :
str1 = 'allows embedded "double" quotes'
print(str1)
Con doppi apici " :
str2 = "allows embedded 'single' quotes"
print(str2)
Con tripli apici ''' o """ :
str3 = """questa stringa si
puo' estendere su piu' righe e puo' includere
apici singoli: ', doppi apici " e altro """
print(str3)
La funzione str converte in stringa (quando e' possibile) un generico altro oggetto.
Di fatto permette la conversione di tipo a stringa.
#da intero
si = str(234)
si
#da float
sf = str(1e-4)
sf
#da liste
sl = str([1,2,3,4,5])
sl
#da complessi
sc = str(1+1j)
sc
funziona con tutti gli oggetti forniti di metodo __str__()
'__str__' in dir(int)
L'indicizzazione funziona come per le liste, con le parentesi quadre
#indicizzazione
str3[3]
#slice
str3[7:14]
str3[7:25]
Notare la notazione /n che fa parte delle cosiddette 'escape sequences':
Oltre a cio' le stringhe sono un tipo di dati che e' caratterizzato da molte funzioni membro, specifiche per il testo:
s = 'questa è una stringa minuscola'
S = s.upper()
S = S.replace('MINUSCOLA','MAIUSCOLA')
print(s)
print(S)
s.count('s'),s.count('S')
Per verificare se la stringa comprende una parola usare l'operatore in:
'stringa' in s , 'Stringa' in s
Per trovare l'inizio della prima occorrenza di una sottostringa:
s.find('stringa'),s.find('Stringa')
s[s.find('stringa'):]
Ci sono due metodi di formattazione di stringhe in Python:
#ASSOCIAZIONE POSIZIONE
'{},{},{}'.format('a',1.0,100)
#la coppia di parentesi graffe delimitano il segnaposto
#se sono vuote inserisco gli argomenti di format nell'ordine in cui sono elencati
#ASSOCIAZIONE INDICE
'{2},{0},{1}'.format('a',1.0,100)
#se dentro i segnaposto ci metto gli indici che individuano gli argomenti
#0 il primo, 1 il seconde etc.
#posso cambiare l'ordinamento dei parametri nei segnaposto:
#oppure posso rferirmi pi ù volte allo stesso parametro:
'{2},{2},{1}'.format('a',1.0,100)
#ASSOCIAZIONE PER NOME
'{c},{a},{b}'.format(a= 'a',b= 1.0,c= 100)
#NB l'associazione del nome deve avvenire all'interno della format
#questo non funzione
#a = 10
#'{a}'.format(a)
#questo invece si', ma attenzione a cosa succede!!
a=10
print('{a}'.format(a='pippo'))
print(a)
#ASSOCIAZIONE PER INDICE+ATTRIBUTO
c = 1+2j
'parte reale: {0.real}, parte immaginaria: {0.imag}'.format(c)
#NB: non funziona con le funzioni membro
#ASSOCIAZIONE PER INDICE + INDICE
l=[1,'due',3.0]
'primo elemento: {0[0]}, secondo: {0[1]}, terzo: {0[2]}'.format(l)
Ci sono altri tipi di associazioni ma si basano su elementi che ancora non abbiamo visto (ad esempio sui dizionari).
La formattazione dei vari campi e' controllabile grazie ad un vero e proprio mini linguaggio di cui vedremo solo qualche esempio:
#float con segno numero minimo di caratteri e precisione 4
'{0:+.4f}'.format(3.123)
#float con segno, 10 caratteri e precisione 1
'{0:+10.1f}'.format(3.123)
#carattere di riempimento'X', allineato a destra, float con segno, 10 caratteri e precisione 1
'{0:X>+10.1f}'.format(3.123)
Vedi anche format examples
In prima istanza le stringhe possono essere utilizzate senza curarsi di come sono rappresentate nella memoria del calcolatore (o come scritte sul disco).
Tuttavia questo e' un aspetto di fondamentale importanza e lo affronteremo qui, almeno a livello di cenni, rimandando ai link esterni per approfondimenti.
Le stringhe sono rappresentate da sequenze di caratteri UNICODE.
La rappresentazione unicode dei caratteri si basa su due aspetti fondamentali:
Il code point non ci dice come stampare un carattere (questo aspetto e' definito dal font utilizzato).
La a minuscola ha code point U+0061 indipendentemente da come e' resa:
Tra gli oltre 100 mila caratteri definiti dalla specifica unicode 6.2 ce ne sono di molti tipi:
oppure il raffinato:
La funzione builtin chr accetta come parametro il code point (intero) e restituisce il carattere ad esso associato (segnala un errore se l'intero e' fuori dal range dei code point validi)
c1 = chr(0x61)
c1
c2 = chr(0x0393)
c2
Posso anche creare un carattere noto il suo nome UNICODE:
c3 = "\N{GREEK CAPITAL LETTER GAMMA}"
c3
Fuori range:
#chr(2000000)
Come sopra ma un po'pi ù elegante:
for code in range(1000000,2000000):
try: #io ci provo ...
chr(code)
except: #se c'e' un errore
print("{} e' l'ultimo codice valido".format(code-1))
print('{} e' fuori dal range dei codici validi'.format(code))
break
La funzione builtin ord accetta come parametro un carattere e restituisce il code point ad esso associato:
c1 = 'a'
c1,ord(c1),hex(ord(c1))
c2 = chr(0x0393)
c3 = "\N{GREEK CAPITAL LETTER GAMMA}"
ord(c2), hex(ord(c2)) , ord(c2) == ord(c3)
[hex(b) for b in chr(0x0393).encode('utf-8')]
La rappresentazione binaria del code point e' detta codifica (encoding) ed e' il secondo aspetto che affrontiamo
Quanti bit sono necessari per rappresentare rappresentare i numeri interi da zero fino a 0x110000 (il code point + grande al sett-2012)?
print(bin(0x110000))
print(len(bin(0x110000)[2:]))#non considero i caratteri 0x
Un altro modo per verificarlo:
import math
math.log(1114111,2)
Ci vogliono almeno 21 bit, quindi minimo 3 byte (se vogliamo utilizzare una codifica a lunghezza fissa), ovviamente se vogliamo rappresentare tutti i possibili code-point.
Tuttavia non tutte le codifiche mirano a rappresentare tutti i caratteri gestiti da unicode
Ad esempio la codifica ASCII mappa solo i primi 128 code point in parole di 7 bit.
Ma vedremo questo fra poco perche' per lavorare con gli encoding e' necessario prima introdurre il tipo di dati denominato bytes.
bytes([255])
bytes(2)
a = bytes([49]) #funzione builtin bytes
b = b'1' #bytes literal
c = '1'.encode('ascii') #codifica di una stringa secondo uno specifico 'encoding'
#stampo la versione 'stampabile' dei bytes
#cioe' stampo la stringa ascii corrispondente
print(a,b,c)
#stampo sia la versione stampabile
#sia l'intero corrispondente
print(a,a[0],bin(a[0]),hex(a[0]))
print(b,b[0],bin(b[0]),hex(b[0]))
print(c,c[0],bin(c[0]),hex(c[0]))
Gli operatori aritmetici e le funzioni membro dei bytes lavorano come per le stringhe
print(a+b) #concatenamento
print(a*5) #ripetizione
# .... etc etc
Se si vuole operare sul valore dei singoli byte e' necessario accedervi singolarmente, ad esempio mediante iterazione:
d = bytes([bi for i,bi in enumerate(range(5))])
print(d)
ricordarsi comunque che non e' possiie eseguire operazioni 'in-place' (per questo ci vogliono i bytearray)
#d[1]=d[1]+1 #errore!!!
d = bytearray([bi for i,bi in enumerate(range(5))])
d[1]=d[1]+1
print(d)
La funzione builtin str oltre a permettere la conversione in stringa di vari tipi di oggetto permette anche di provare vari encoding.
Se viene specificato il parametro 'encoding' restituisce la rappresentazione come stringa della sequenza di bytes passata come primo argomento, secondo l'encoding specificato.
Come sempre meglio un esempio di mille parole:
a = bytes([1])
b = b'1'
str(a,encoding='ascii') , str(b,encoding='ascii')
#str(b'\xe4',encoding='latin-1')
#tutti i caratteri ascii
nch=128
#str(bytes(range(nch)),encoding='ascii')
nch=256 #provare con 257
#str(bytes(range(256)),encoding='latin-1')
#bytes litteral diversi, stessa stringa con diversi encoding
s1 = b'perch\xe9'.decode('latin-1')
s2 = b'perch\xc3\xa9'.decode('utf-8')
print(s1)
print(type(s1))
print(s1 == s2)
#bytes uguali -> stringhe diverse con diversi encoding
#print(bytes([195,169]).decode('latin-1'))
#print(bytes([195,169]).decode('utf-8'))
Per 'giocare' un po con gli encoding possiamo usare le funzioni encode del tipo str
stringa = 'perch é '
print(stringa.encode('latin-1'))
print(stringa.encode('utf-8'))
print("come si pu ò vedere ottengo due diverse rappresentazioni della stessa stringa")
stampo i bytes come interi:
stringa = "perch é "
print([b for b in stringa.encode('latin-1')])
print([b for b in stringa.encode('utf-8')])
b1=bytes('perch é ',encoding='latin-1')
b2=bytes('perch é ',encoding='utf-8')
print(b1)
print(b2)
e con questo, vi saluto: