Proposta di Calendario: Quando Giovedi 17 Aprile (Martedi 22 Aprile) Giovedi 24 Aprile Martedi 29 Aprile (Giovedi 1 Maggio) Martedi 6 Maggio Giovedi 8 Maggio Martedi 13 Maggio Oppure Giovedi 15 Maggio Dove L3 -‐-‐-‐ L3 T7 -‐-‐-‐ T7 L3 T7 Cosa Esercitazione 6 Vacanze di Pasqua Esercitazione 7 Lezione 6 + Esercitazione 8 Festa (Primo Maggio) Esercitazione 9 Esercitazione 10 Esonero 2 Chiarimenti su esercizi dell’esonero L’istruzione print stampa valori, variabili (se esistono) o il risultato di un comando, se questo è sintatticamente corretto >>> print "10" + "10" 1010 >>> print "10" * "10" Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: can't multiply sequence by non-int of type 'str' >>> print "10" * 10 10101010101010101010 >>> print "10" * 10.0 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: can't multiply sequence by non-int of type 'float' >>> print "10" * int(10.0) 10101010101010101010 >>> print 1 = 1 File "<stdin>", line 1 print 1 = 1 ^ SyntaxError: invalid syntax >>> print 1 == 1 True Data una sequenza di una proteina, calcolare e stampare qual’è l’aminoacido con frequenza maggiore >>> seq = "ARTFRCCRRGHILRMPQEEST" >>> max_freq = 0 >>> l = len(seq) >>> for amino in seq: ... freq = seq.count(amino)/float(l) ... if freq > max_freq: ... max_freq = freq ... max_amino = amino ... >>> print max_amino,max_freq R 0.238095238095 Data la sequenza di una proteina, e data una lista contenente gli aminoacidi idrofilici [“E”, “D”, “R”, “K”, “H”, “S”, “T”, “Y”, “N”, “Q”], calcolare e stampare la frequenza degli aminoacidi idrofilici trovati nella proteina se è maggiore di 0.2 >>> seq = "AREFDEDDRGHDDICMEPQEDECT" >>> idrofilici = ["E","D","R","K","H","S","T","Y","N","Q"] >>> for amino in idrofilici: ... frequenza = seq.count(amino)/float(len(seq)) ... if frequenza > 0.2: ... print amino,frequenza ... E 0.208333333333 D 0.25 Date due liste della stessa lunghezza e contenenti numeri interi, calcolate e la somma degli elementi nella stessa posizione nelle due liste (ad es. la somma del primo elemento della prima lista e del primo elemento della seconda lista, poi la somma del secondo elemento della prima lista e del secondo elemento della seconda lista, e cosi via), e stampatela solo se maggiore di 5 >>> L1 = [4,1,6,8,2] >>> L2 = [0,6,2,8,3] >>> for i in range(0,len(L1),1): ... somma = L1[i] + L2[i] ... if somma > 5: ... print somma ... 7 8 16 Data la sequenza di una proteina, stampare la posizione nella sequenza di tutte le cisteine (“C”) >>> seq = "ARTFRCCRRGHILRCMPQEEST" >>> for i in range(0,len(seq),1): ... if seq[i] == "C": ... print i ... 5 6 14 Data la sequenza di una proteina, ad es. “AREFDEDDRGHDDICMEPQEDECT”, e data una lista contenente gli aminoacidi idrofilici [“E”, “D”, “R”, “K”, “H”, “S”, “T”, “Y”, “N”, “Q”], stampare la sequenza della proteina, in cui tutti gli aminoacidi idrofilici sono scritti con lettere minuscule e tutti gli altri maiuscoli. >>> seq = "AREFDEDDRGHDDICMEPQEDECT" >>> idrofilici = ["E","D","R","K","H","S","T","Y","N","Q"] >>> nuova_seq = "" >>> for amino in seq: ... if amino in idrofilici: ... nuova_seq = nuova_seq + amino.lower() ... else: ... nuova_seq = nuova_seq + amino ... >>> print nuova_seq AreFdeddrGhddICMePqedeCt Data la sequenza di una proteina, ad es. “ARTYCWCRGHITLMPYQEEST”, e data una lista contenente gli aminoacidi aromatici [“F”, ”W”, “Y”], stampate quante volte si trova ciascun aminoacido aromatico nella sequenza della proteina solo se l’aminoacido aromatico è presente nella proteina, altrimenti stampate che l’aminoacido non è stato trovato nella proteina >>> seq = "ARTYCWCRGHITLMPYQEEST" >>> aromatici = ["F","W","Y"] >>> for amino in aromatici: ... conte = seq.count(amino) ... if conte > 0: ... print amino,conte ... else: ... print amino,"non trovato" ... F non trovato W 1 Y 2 Metodi Informatici per la Biologia 5. Funzioni e dizionari Che cosa impareremo in questo modulo: -‐ A incapsulare del codice che svolge una funzione specifica per riutilizzarlo quando serve -‐ Un nuovo tipo di dato che fornisce un contenitore di oggetti etichettati: i dizionari 5.1 Le funzioni Quando si scrive un programma che ha per scopo la risoluzione di un problema, è buona abitudine scomporre il problema in sottoproblemi, e risolverne uno per volta con un pezzo di codice dedicato. Questo codice che risolve uno specifico problema può essere incapsulato in una funzione, cioè un blocco di codice con un nome che prende in input degli argomenti e restituisce uno o più valori (o nessuno). Una delle forze maggiori della programmazione è la possibilità di riutilizzare codice scritto in precedenza, in modo da non dover sempre risolvere gli stessi problemi. Ciò può essere fatto semplicemente copiando e incollando frammenti di codice, ma il modo migliore è scrivere il più possibile del codice in forma generale, dedicare funzioni specifiche a ogni specifico compito, salvare queste funzioni in un file, e riutilizzare queste funzioni importandole da questo file nel programma che stiamo scrivendo. Tutto ciò è analogo all’utilizzo delle librerie che abbiamo visto durante le prime lezioni. Ad esempio, la libreria math fornisce funzioni che possono essere importate con il comando import. Stessa cosa si può fare con funzioni create dal programmatore, salvate in una libreria e importate da questa libreria. La forma generale di una funzione è: def nome_funzione(argomento1, argomento2,…): "documentazione" istruzione1 istruzione2 … istruzione n return variabile1, variabile2, … Generalmente una funzione richiede in input uno o più argomenti, che possono essere di qualsiasi tipo (ad es. numeri, stringhe, liste, scritti esplicitamente o come variabili cui questi valori sono stati assegnati). Non è obbligatorio per una funzione prendere argomenti, alcune funzioni possono non richiedere nessun argomento in input, ma è importante che se una funzione richiede n argomenti, gli siano in effetti passati n argomenti, altrimenti si ottiene un errore. Il comando def crea la funzione, e deve essere seguito da un nome che si vuole usare per identificare la funzione (che sia più possibile informativo di ciò che la funzione fa), dagli argomenti che la funzione richiede, tra parentesi tonde, seguiti da due punti ( : ). Il corpo della funzione, cioè il blocco d’istruzioni, va indentato di una tacca rispetto al livello zero (il bordo sinistro). Se nel corpo della funzione si scrivono istruzioni che richiedono indentazione (ad es. un ciclo for, un controllo if...else..), questi vanno indentati ulteriormente di un’altra tacca. Se si vuole usare una funzione, la si deve chiamare, cioè scrivere il suo nome seguito, fra parentesi tonde, dagli argomenti di cui la funzione ha bisogno. Vediamo come esempio una funzione che prende in input due numeri e restituisce la loro somma: >>> def somma_due_numeri(a,b): ... "prende in input due numeri e ne restituisce la somma" ... somma = a+b ... return somma ... >>> print somma_due_numeri(10,20) 30 >>> print somma_due_numeri.__doc__ 'prende in input due numeri e ne restituisce la somma' >>> c = somma_due_numeri(12,3) >>> print c 15 >>> print somma_due_numeri(11,25,33) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: somma_due_numeri() takes exactly 2 arguments (3 given) >>> #la funzione vuole solo 2 argomenti >>> print b #b esiste solo nella funzione Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'b' is not defined >>> somma_due_numeri = somma_due_numeri(10,20) >>> #sto sovrascrivendo il nome somma_due_numeri >>> print somma_due_numeri 30 >>> print somma_due_numeri(10,20) Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'int' object is not callable >>> type(somma_due_numeri) <type 'int'> Come si vede nell’esempio, la funzione somma_due_numeri prende due argomenti che sono i due numeri da sommare. Quando questi due numeri sono passati alla funzione, vengono assegnati a due variabili a e b che sono interne alla funzione, cioè se proviamo a richiamare a o b fuori dalla funzione otteniamo un errore. La funzione prende esattamente due valori in input, quindi se gliene diamo di più o di meno si ottiene un errore. Per facilitare il riutilizzo del codice, si può scrivere una documentazione della funzione, cioè un testo che descrive ciò che fa la funzione e che è richiamabile con il solito comando nome_funzione.__doc__ La funzione somma_due_numeri restituisce una variabile contenente la loro somma con il comando return, questa variabile è però interna alla funzione, e se vogliamo utilizzarla nel resto del programma, va a sua volta assegnata a un’altra variabile. Si parla in questo caso di variabili locali, che sono interne alle funzioni e non accessibili dal codice esterno alla funzione. Se la funzione non ritorna niente, i risultati delle istruzioni interne alla funzione non sono accessibili fuori dalla funzione stessa. L’istruzione return non è obbligatoria, alcune funzioni non hanno bisogno di restituire dati, quindi se ci si dimentica l’istruzione di return questo non genera errori di sintassi, ma possono però essere errori logici. >>> def somma_due_numeri(a,b): ... "prende in input due numeri e ne restituisce la somma" ... somma = a+b ... >>> print somma_due_numeri(11,3) None L’istruzione return può restituire più di un valore, e in tal caso vanno separati da virgola. Quando una funzione restituisce più di un valore, questi sono forniti sotto forma di tupla, che è un nuovo tipo di dato, per certi versi simile alle liste che abbiamo già visto. Una tupla è un contenitore ordinato e immutabile, mentre una lista è ordinata ma mutabile, cioè permette di modificarne il contenuto, e ciò non è possibile per le tuple. Vediamo un esempio di funzione che ritorna due valori: >>> def somma_al_quadrato(a,b): ... sum = a+b ... sum_sq = (a+b)**2 ... return sum,sum_sq ... >>> sum = somma_al_quadrato(10,5) >>> print sum (15, 225) >>> type(sum) <type 'tuple'> >>> sum[0] 15 >>> sum[1] 225 A che servono le tuple? In generale, sono contenitori che si usano quando si sa che il contenitore non dovrà essere modificato, e si vuole prevenire l’accidentale modifica. Se ad esempio creo una lista per contenere una serie di valori, e poi per sbaglio compio un’operazione di cancellazione su un elemento della lista, magari perché ho usato da qualche parte del programma un’altra lista dal nome simile, ciò non mi darà un errore di sintassi ma è però un errore logico, molto difficile da scoprire perché il programma gira bene, ma il suo comportamento sarà sbagliato. Inoltre, alcuni tipi di operazioni in Python possono essere solo compiuti da tipi di dato immutabili, ad esempio le chiavi di un dizionario possono essere numeri, stringhe, tuple ma non liste (i dizionari li vedremo fra poco). Le tuple possono essere create scrivendo un elenco di elementi separato da virgole fra parentesi tonde, invece che quadre. Per il resto le tuple si comportano come le liste, cioè sono indicizzate e permettono accesso a specifici elementi con il loro indice: >>> tup = (1,2,3,"a","c",100,10.5) >>> len(tup) 7 >>> print tup[0] 1 >>> print tup[-1] 10.5 >>> print tup[0:5] (1, 2, 3, 'a', 'c') >>> print tup[4:len(tup)] ('c', 100, 10.5) >>> for elemento in tup: ... print elemento ... 1 2 3 a c 100 10.5 >>> tup.append(10000) Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'tuple' object has no attribute 'append' >>> tup[1] = 1000 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'tuple' object does not support item assignment Torniamo alla nostra funzione. Se ciò che è restituito da una funzione è assegnato a una variabile, e se la funzione ritorna più di un valore, la variabile sarà una tupla contenente tutti i valori restituiti dalla funzione. Alternativamente, si può assegnare ogni valore restituito dalla funzione a una specifica variabile. In questo caso, il numero di variabili utilizzate deve essere lo stesso di quello delle variabili restituite, altrimenti incorro in un errore (cioè se la funzione ritorna due valori, ho bisogno di assegnare il risultato della funzione a due variabili, e cosi via). >>> def somma_al_quadrato(a,b): ... sum = a+b ... sum_sq = (a+b)**2 ... return sum,sum_sq ... >>> s = somma_al_quadrato(10,5) >>> print s (15, 225) >>> s1,s2 = somma_al_quadrato(10,5) >>> print s1 15 >>> print s2 225 >>> s1,s2,s3 = somma_al_quadrato(10,5) Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: need more than 2 values to unpack La funzioni viste finora prendono due numeri come argomenti. Come si fa se voglio sommare 3, 4 o un numero qualsiasi di numeri? Ovviamente non è conveniente scrivere una funzione per sommare due numeri, una per sommarne tre, un’altra che è capace di sommarne quattro, e cosi via. Ci sono due modi: o scrivere una funzione che prenda in input una lista contenente i numeri da sommare, oppure usare una sintassi speciale che comunica alla funzione di aspettarsi un numero variabile di argomenti. >>> def somma_lista(Lista): ... somma = 0 ... for elemento in Lista: ... somma = somma + elemento ... return somma ... >>> L = [1,2,3,4,5] >>> s = somma_lista(L) >>> print s 15 >>> def somma_numeri(*numeri): ... somma = 0 ... for numero in numeri: ... somma = somma + numero ... return somma ... >>> print somma_numeri(1,2,3,4,5) 15 Nel primo caso ho passato alla funzione una lista, che ho creato e usato come argomento. Nel secondo, ho definito la funzione come capace di accettare un numero imprecisato di argomenti con la sintassi *nome_variabile, e chiamando la funzione passandole quanti argomenti voglio. Questa sintassi in pratica converte tutti gli argomenti in una tupla. Un’altra possibilità è utilizzare valori di default per alcuni o tutti gli argomenti. Vediamo come esempio una funzione che calcola la potenza di un numero in input: se la funzione è chiamata con un solo argomento, viene restituito il valore dell’argomento al quadrato, se la funzione è chiamata con due argomenti, viene restituito il valore del primo elevato il valore del secondo: >>> def potenza(numero,esponente=2): ... p = numero ** esponente ... return p ... >>> print potenza(10) 100 >>> print potenza(10,4) 10000 In questo caso quando ho definito la funzione, ho assegnato all’argomento esponente valore di default uguale a 2 con la sintassi generale nome_argomento = valore. Il numero di argomenti con assegnato valore di default può essere qualsiasi. >>> def potenza(numero=10,esponente=2): ... p = numero ** esponente ... return p ... >>> print potenza() 100 In questo caso, anche il valore del numero da elevare a potenza è settato per default, quindi se chiamo la funzione senza argomenti, ottengo il risultato usando i valori di default dei due argomenti. Usare valori di default può essere utile quando la funzione che si scrive andrà utilizzata quasi sempre allo stesso modo, ma la voglio comunque scrivere in maniera abbastanza generale da poterla usare in tutti i contesti. Infine, è possibile scrivere e salvare una funzione in un file e importarla, come fosse un metodo di una qualsiasi libreria. Ad esempio, immaginiamo di aver salvato in un file che ho chiamato le_mie_funzioni.py il seguente codice: def media(L): somma = sum(L) elementi = len(L) media = float(somma)/elementi return media Ora, nell’interprete interattivo o in un altro programma posso importare la funzione media, nel seguente modo: >>> import le_mie_funzioni >>> L = [1,2,3,4,5] >>> print le_mie_funzioni.media(L) 3.0 5.2 I dizionari Introduciamo un altro tipo di dato, il dizionario, che è un contenitore disordinato e mutabile di oggetti etichettati. La forma generale è {chiave1:valore,chiave2:valore}, in cui la chiave è associata univocamente ad un valore che può essere richiamato dal dizionario. Vediamo alcuni esempi: >>> dati = {"Nome":"Fabrizio","Cognome":"Ferre","Studio":320} >>> print dati["Nome"] Fabrizio >>> print dati["Studio"] 320 >>> dati[0:1] Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: unhashable type >>> dati[0] Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 0 Le coppie chiave:valore di un dizionario vanno scritte tra parentesi graffe e separate da virgole. Un dizionario è disordinato, perché l’ordine d’inserimento dei dati non è mantenuto. E’ mutabile, perché si possono rimuovere o inserire coppie chiave:valore, o associare un nuovo valore a una chiave già esistente. Questo implica che non ci possono essere chiavi uguali in un dizionario: se assegno due valori diversi alla stessa chiave, il secondo valore immesso sovrascrive il vecchio. >>> dati = {"Nome":"Fabrizio","Cognome":"Ferre","Studio":320} >>> dati["indirizzo"] = "Sogene" >>> del dati["Cognome"] >>> print dati {'indirizzo': 'Sogene', 'Studio': 320, 'Nome': 'Fabrizio'} >>> dati["Nome"] = "Ciccio" >>> print dati["Nome"] Ciccio >>> chiavi = dati.keys() >>> print chiavi ['indirizzo', 'Studio', 'Nome'] >>> valori = dati.values() >>> print valori ['Sogene', 320, 'Ciccio'] >>> for chiave in chiavi: ... print chiave,dati[chiave] ... indirizzo Sogene Studio 320 Nome Ciccio Se si cerca di accedere a un valore di un dizionario usando una chiave che non è presente nel dizionario, si ottiene un errore. Si può però controllare se una chiave c’è o no nel dizionario con l’operatore logico in, e farsi stampare il valore a essa associato solo se c’è in un costrutto if … else … >>> dati["Cognome"] Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 'Cognome' >>> "Cognome" in dati False >>> if "Cognome" in dati: ... print dati["Cognome"] ... else: ... print "La chiave cercata non c'e'" ... La chiave cercata non c'e' I dizionari sono utili quando si vogliono immagazzinare in forma rapidamente accessibile dei dati organizzati in modo da avere una serie di oggetti associati a delle caratteristiche. Usiamo un dizionario per contenere il codice genetico, cioè le associazioni fra codoni e rispettivi aminoacidi, e per ottenere la traduzione di una sequenza di RNA: >>> codice_genetico = { ... 'GCU':'A','GCC':'A','GCA':'A','GCG':'A','CGU':'R', ... 'CGC':'R','CGA':'R','CGG':'R','AGA':'R','AGG':'R', ... 'UCU':'S','UCC':'S','UCA':'S','UCG':'S','AGU':'S', ... 'AGC':'S','AUU':'I','AUC':'I','AUA':'I','AUU':'I', ... 'AUC':'I','AUA':'I','UUA':'L','UUG':'L','CUU':'L', ... 'CUC':'L','CUA':'L','CUG':'L','GGU':'G','GGC':'G', ... 'GGA':'G','GGG':'G','GUU':'V','GUC':'V','GUA':'V', ... 'GUG':'V','ACU':'T','ACC':'T','ACA':'T','ACG':'T', ... 'CCU':'P','CCC':'P','CCA':'P','CCG':'P','AAU':'N', ... 'AAC':'N','GAU':'D','GAC':'D','UGU':'C','UGC':'C', ... 'CAA':'Q','CAG':'Q','GAA':'E','GAG':'E','CAU':'H', ... 'CAC':'H','AAA':'K','AAG':'K','UUU':'F','UUC':'F', ... 'UAU':'Y','UAC':'Y','AUG':'M','UGG':'W','UAG':'STOP', ... 'UGA':'STOP','UAA':'STOP'} >>> sequenza = "CGAUCGUACGAUGUCAGCUGCUGCUACGUA" >>> proteina = "" >>> for i in range(0,len(sequenza),3): ... codone = sequenza[i:i+3] ... aminoacido = codice_genetico[codone] ... proteina = proteina + aminoacido ... >>> print proteina RSYDVSCCYV Oppure calcoliamo l’idrofobicità media della proteina che abbiamo appena tradotto usando la scala di idrofobicità di Kyte-‐Doolittle: >>> scala_idrofobicita = {'N':-3.5,'P':-1.6,'Q':-3.5,'A':1.8, ...'R':-4.5,'S':-0.8,'C':2.5,'T':-0.7,'D':-3.5, ...'E':-3.5,'V':4.2,'F':2.8,'W':-0.9,'G':-0.4,'H':-3.2, ...'Y':-1.3,'I':4.5,'K':-3.9,'L':3.8,'M':1.9} >>> somma_idro = 0 >>> for aminoacido in proteina: ... idrofobicita = scala_idrofobicita[aminoacido] ... somma_idro = somma_idro + idrofobicita ... >>> media_idrofobicita = float(somma_idro) / len(proteina) >>> print media_idrofobicita 0.12 Immaginiamo di aggiungere al file le_mie_funzioni.py i dizionari con il codice genetico e la scala di idrofobicità, e il codice che traduce l’RNA e calcola l’idrofobicità media sotto forma di funzioni: scala_idrofobicita = {'N':-3.5,'P':-1.6,'Q':-3.5,'A':1.8, 'R':-4.5,'S':-0.8,'C':2.5,'T':-0.7,'D':-3.5, 'E':-3.5,'V':4.2,'F':2.8,'W':-0.9,'G':-0.4,'H':-3.2, 'Y':-1.3,'I':4.5,'K':-3.9,'L':3.8,'M':1.9} codice_genetico = { 'GCU':'A','GCC':'A','GCA':'A','GCG':'A','CGU':'R', 'CGC':'R','CGA':'R','CGG':'R','AGA':'R','AGG':'R', 'UCU':'S','UCC':'S','UCA':'S','UCG':'S','AGU':'S', 'AGC':'S','AUU':'I','AUC':'I','AUA':'I','AUU':'I', 'AUC':'I','AUA':'I','UUA':'L','UUG':'L','CUU':'L', 'CUC':'L','CUA':'L','CUG':'L','GGU':'G','GGC':'G', 'GGA':'G','GGG':'G','GUU':'V','GUC':'V','GUA':'V', 'GUG':'V','ACU':'T','ACC':'T','ACA':'T','ACG':'T', 'CCU':'P','CCC':'P','CCA':'P','CCG':'P','AAU':'N', 'AAC':'N','GAU':'D','GAC':'D','UGU':'C','UGC':'C', 'CAA':'Q','CAG':'Q','GAA':'E','GAG':'E','CAU':'H', 'CAC':'H','AAA':'K','AAG':'K','UUU':'F','UUC':'F', 'UAU':'Y','UAC':'Y','AUG':'M','UGG':'W','UAG':'STOP', 'UGA':'STOP','UAA':'STOP'} def traduci_rna(sequenza): proteina = "" for i in range(0,len(sequenza),3): codone = sequenza[i:i+3] aminoacido = codice_genetico[codone] proteina = proteina + aminoacido return proteina def idrofobicita_media(proteina): somma_idro = 0 for aminoacido in proteina: idrofobicita = scala_idrofobicita[aminoacido] somma_idro = somma_idro + idrofobicita media_idrofobicita = float(somma_idro) / len(proteina) return media_idrofobicita Ora posso importare la libreria le_mie_funzioni.py e utilizzare sia le funzioni che i dizionari in esso contenuti: >>> from le_mie_funzioni import * >>> proteina = traduci_rna("ACGUUGCGCGGACGAUCUAUCUCUACU") >>> print proteina TLRGRSIST >>> mrna = "CGAUCGAUGCUACGAUGCUCG" >>> print idrofobicita_media(traduci_rna(mrna)) -0.46 >>> scala_idrofobicita["A"] 1.8 >>> scala_idrofobicita["F"] 2.8 >>> scala_idrofobicita["W"] -0.9 >>> scala_idrofobicita["C"] 2.5 >>> codice_genetico["AAA"] 'K' >>> codice_genetico["UAG"] 'STOP' >>> codice_genetico["UUU"] 'F'
© Copyright 2024 Paperzz