# --- # jupyter: # jupytext: # formats: ipynb,py:percent # text_representation: # extension: .py # format_name: percent # format_version: '1.3' # jupytext_version: 1.15.2 # kernelspec: # display_name: Python 3 (ipykernel) # language: python # name: python3 # --- # %% [markdown] slideshow={"slide_type": "slide"} # # Fondamenti di Programmazione # # # **Andrea Sterbini** # # lezione 11 - 30 ottobre 2023 # %% [markdown] slideshow={"slide_type": "subslide"} # # RECAP: Files # ```python # # apro il file e metto nella variabile F l'oggetto che ottengo # with open(, mode=, encoding='utf8') as F: # codice che legge/scrive il file F # # uscendo dal with il file viene chiuso automagicamente # ``` # Una volta aperto il file **F** potete usare i metodi: # - **`F.read()`** legge tutto il contenuto (o una sua parte) # - **`F.readline()`** legge UNA riga (compreso **`'\n'`**) # - **`F.readlines()`** legge TUTTE le righe (compresi **`'\n'`**) e torna una lista # - **`F.write()`** scrive un testo nel file (dovete aggiungere voi `'\n'`) # - **`print( ..., file=F)`** stampa nel file (molto comodo) # - **`F.seek(0)`** torna alla posizione 0 (inizio) # %% [markdown] slideshow={"slide_type": "subslide"} # # ANALISI: Come trovare il file più importante dato un gruppo di parole cercate # **PURTROPPO** Le parole più frequenti sono in genere troppo comuni e poco significative # (con poco contenuto semantico) # # Esempio: articoli, verbi ausiliari, preposizioni, avverbi, parole comuni ... # # Quindi una parola sarà "interessante" se **appare spesso in un file** ma **appare in pochi file**. # Calcoliamo: # - appare spesso in un file: **Term Frequency** (tf) la frequenza percentuale della parola nel file # - appare in pochi file: **Inverse Document Frequency** (idf) la percentuale di file che la contiene # # Vedi https://en.wikipedia.org/wiki/tf-idf # %% [markdown] slideshow={"slide_type": "subslide"} # ## TF: Term Frequency (frequenza % di una parola in ciascun file) # - per ciascun file # - contiamo le parole # - dividiamo per il numero totale di parole # %%% analisi del testo e ricerca slideshow={"slide_type": "subslide"} # ho un elenco di documenti con il loro encoding, ottenuto unzippando files.zip files : dict[str,str] = { 'files/holmes.txt' : 'utf-8-sig', 'files/alice.txt' : 'utf-8-sig', 'files/frankenstein.txt' : 'utf-8-sig', 'files/alice_it.txt' : 'latin', 'files/prince.txt' : 'utf-8-sig' } # %%%% - estrazione delle parole (rimpiazzando i nonalpha con spazi) slideshow={"slide_type": "subslide"} # per estrarre le parole da un file che contiene anche segni di interpunzione def estrai_parole(filename : str, encoding : str) -> list[str]: # apro il file in lettura e ne leggo il contenuto with open(filename, encoding=encoding) as FILE: text = FILE.read() # se voglio lo converto in lowercase (ma potrei preferire di no) text = text.lower() # calcolo l'insieme dei caratteri presenti nel testo caratteri = set(text) # ne estraggo solo i caratteri non-alpha e creo un dizionario per translate nonalfa = { c: ' ' for c in caratteri if not c.isalpha() } # li rimpiazzo tutti con spazio con translate e maketrans text = text.translate(str.maketrans(nonalfa)) # uso split sul testo in cui ho cambiato tutti i non-apha in spazi return text.split() # esempio: parole del libro Alice nel paese delle meraviglie parole = estrai_parole('files/alice_it.txt', 'latin') ### NOTA: come usare meno memoria? lavorando riga per riga print(parole[:30]) # %%%% - mi serve l'importanza di una parola in un testo slideshow={"slide_type": "subslide"} # per contare quante sono le parole in percentuale def conta_parole(parole : list[str] ) -> dict[str,float]: # trovo le parole uniche usando un insieme parole_uniche = set(parole) # e costruisco il dizionario parola:conteggio usando count ### INEFFICIENTE!!! N = len(parole) return { parola: parole.count(parola)/N for parola in parole_uniche } # rifaccio i conti sulle parole di Alice conteggi = conta_parole(parole) print('alcuni conteggi:', list(conteggi.items())[:20],'\n') # le 50 parole più presenti in Alice sono: più_presenti = sorted(conteggi, reverse=True, key=lambda K: conteggi[K])[:50] print('le 50 più presenti sono:', più_presenti) # %% [markdown] # ### Vedete che le parole più presenti sono anche le più comuni e meno significative # %%%% per ogni file costruiamo il dizionario delle frequenze delle sua parole slideshow={"slide_type": "subslide"} # frequenza delle parole nel file iesimo # - tf_i = # presenze / # parole del file # per tutti i file costruiamo il dizionario del numero di files in cui appaiono frequenze : dict[str,dict[str,float]]= {} # conteggio delle parole nei file: frequenze[name][parola] -> % di parole # conto le frequenze di tutte le parole contenute in ciascun file for filename, encoding in files.items(): print(filename) parole = estrai_parole(filename, encoding) conteggio = conta_parole(parole) frequenze[filename] = conteggio # %% [markdown] slideshow={"slide_type": "subslide"} # ## DF: Document Frequency (% di documenti che contengono la parola) # %%%% per ogni file costruiamo il dizionario delle frequenze delle sua parole slideshow={"slide_type": "subslide"} # conto i file che contengono una parola # presenze[parola] -> # di file che la contengono presenze : dict[str,float] = {} for filename in files: for parola in frequenze[filename]: presenze[parola] = presenze.get(parola, 0) + 1 # divido per il numero di file per avere la presenza % Nfile = len(files) for parola in presenze: presenze[parola] /= Nfile print('esempio delle DF:', list(presenze.items())[:30]) print('\nparole presenti in tutti i file:', *(p for p,f in presenze.items() if f == 1)) # %% [markdown] # ### IDF: Inverse Document Frequency = logaritmo dell'inverso della Document Frequency # - **meno** documenti contengono la parola **più è grande l'inverso** e quindi il logaritmo (anche se cresce lentamente) # - **più** documenti contengono la parola **più è piccolo l'inverso e il logaritmo** # # Le parole **rare** sono più interessanti (hanno IDF più grande) # %%%% - importanza delle parole nei idf file = tf_i * slideshow={"slide_type": "subslide"} from math import log # log dell'inverso della frequenza nei documenti # - idf = log( # di file / # di file che contengono la parola ) IDF : dict[str,float] = { parola : log(1/quante) for parola,quante in presenze.items() } list(IDF.items())[:30] # NOTA: ogni parola appare in almeno UN file quindi 1/N è sempre calcolabile # %%%% - importanza delle parole nei idf [markdown] file = tf_i * slideshow={"slide_type": "subslide"} # ### finalmente posso vedere quale file "pesa di più" rispetto alle parole della query # # - per ciascun file # - e per ciascuna parola di una query # - ne prendo la frequenza % nel file (DF) # - la moltiplico per IDF della parola # - sommando questi contributi ottengo quanto quel file è utile per quella specifica query # %%%% - importanza delle parole nei idf file = tf_i * slideshow={"slide_type": "subslide"} # Esempio di query query : list[str] = [ 'clock', 'hat', 'violin'] pesi : dict[str,float] = {} for file in frequenze: importanza = 0.0 for parola in query: df = frequenze[file].get(parola,0) # DF della parola nel file (0 se assente) idf = IDF.get(parola,0) # IDF della parola nei file (0 se assente) # aggiungo il peso della parola per quel file ovvero df*idf importanza += df * idf pesi[file] = importanza # importanza del file per questa query # ordino e stampo i file per peso decrescente for file,peso in sorted(pesi.items(), reverse=True, key=lambda coppia: coppia[1]): print( f"{file:25} {peso:0.5%}") # %% [markdown] # # INTERVALLO: Quesito con la Susi 940 # ![](Susi-940.png) # %% [markdown] # ## Bisogna trovare la terna di valori che: # - devono essere diversi e compresi in [1,15] # - il loro prodotto == alla somma degli altri 12 # - A è pari, B e C sono dispari # - A == 2*B # %% [markdown] # - prendo un valore A **pari** tra 1 e 15 # - prendo B = A//2 # - prendo C dispari tra 1 e 15 diverso da B # - se A*B*C == somma degli altri # - stampo A,B,C # # NOTA: la somma di tutti i 15 valori -A-B-C è la somma dei rimanenti # %% def susi_940(): SOMMA = sum(range(1,16)) # oppure (1+15)*15//2=120 for A in range(2,15,2): # scandisco i pari tra 1 e 15 B = A//2 for C in range(1,16,2): # scandisco i dispari tra 1 e 15 if C == B: continue # se C == B lo ignoro if A*B*C == SOMMA -A-B-C : print(A,B,C) susi_940() # %% [markdown] # # Proviamo con Wolfram Alpha # # ![](alpha-query.png) # # ![](alpha-answer.png) # %% [markdown] # ## Più in generale, per trovare TUTTE le terne in una sequenza 1..N # Facciamo in modo che siano $A) -> dict | list | tipo_base`** # - **`json.dump(, )`** # # Personalizzabile anche per altri tipi di oggetti (non ovvio, per casa per chi è interessato) # %% slideshow={"slide_type": "subslide"} # posso anche leggere da una stringa in formato JSON con la funzione 'loads' XX = json.loads(''' [{"nome": "Minnie", "cognome": "Mouse", "telefono": "555-54321", "indirizzo": "via di M.me Curie 1", "città": "Topolinia"}, {"nome": "Pippo", "cognome": "de' Pippis", "telefono": "555-33333", "indirizzo": "via dei Pioppi 1", "città": "Topolinia"}] ''') print(XX) # oppure produrre la stringa in formato JSON da una struttura Python print(json.dumps(XX)) # %% slideshow={"slide_type": "subslide"} # o produrre una stringa JSON da un oggetto Python json.dumps(XX) # %% slideshow={"slide_type": "subslide"} # Un file json può contenere valori semplici (int, float, str, True=true, False=false, None=null) print(json.dumps(None)) print(json.dumps([False, True])) ## NOTA: le stringhe DEVONO essere racchiuse da doppi apici print(json.dumps('Pape"rino')) # %%% file json slideshow={"slide_type": "subslide"} # Esempio: data una tabella come lista di dizionari agenda = [ {'nome': 'Paperino','cognome':'Paolino', 'telefono':'555-1313', 'indirizzo': 'via dei Peri 113', 'città': 'Paperopoli'}, {'nome': 'Gastone', 'cognome':'Paperone', 'telefono':'555-1717', 'indirizzo': 'via dei Baobab 42', 'città': 'Paperopoli'}, {'nome': 'Paperon', 'cognome':"de' Paperoni", 'telefono':'555-99999', 'indirizzo': 'colle Papero 1', 'città': 'Paperopoli'}, {'nome': 'Archimede','cognome':'Pitagorico', 'telefono':'555-11235', 'indirizzo': 'colle degli Inventori 1', 'città': 'Paperopoli'}, {'nome': 'Pietro', 'cognome':'Gambadilegno', 'telefono':'555-66666', 'indirizzo': 'via dei Ladri 13', 'città': 'Topolinia'}, {'nome': 'Trudy', 'cognome':'Gambadilegno', 'telefono':'555-66666', 'indirizzo': 'via dei Ladri 13', 'città': 'Topolinia'}, {'nome': 'Topolino','cognome':'Mouse', 'telefono':'555-12345', 'indirizzo': 'via degli Investigatori 1', 'città': 'Topolinia'}, {'nome': 'Minnie', 'cognome':'Mouse', 'telefono':'555-54321', 'indirizzo': 'via di M.me Curie 1', 'città': 'Topolinia'}, {'nome': 'Pippo', 'cognome':"de' Pippis", 'telefono':'555-33333', 'indirizzo': 'via dei Pioppi 1', 'città': 'Topolinia'}, ] # %%% file json slideshow={"slide_type": "subslide"} import json # prima la salvo nel file with open('agenda.json', encoding='utf8', mode='w') as F: json.dump(agenda, F) # !cat agenda.json # %%% file json slideshow={"slide_type": "subslide"} # poi la ricarico with open('agenda.json', encoding='utf8') as F: L1 = json.load(F) L1[:2] # e ne mostro i primi 2 elementi # %% [markdown] slideshow={"slide_type": "subslide"} # # File [YAML](https://pyyaml.org/wiki/PyYAMLDocumentation) (un altro modo di rappresentare dati annidati in testo semplice) # - dizionari (una **chiave `:` valore** per ciascuna riga) # - liste ( valori su righe diverse, preceduti da **`-`**) # - dati semplici (str senza apici se possibile, True, False, null=None, interi, float) # - documenti multipli # - generici oggetti Python (advanced) # # Per annidare le strutture si usa l'**indentazione** # %% slideshow={"slide_type": "subslide"} import yaml with open('agenda.yaml', mode='w', encoding='utf8') as F: yaml.dump(agenda[:2], F) # !cat agenda.yaml # %% [markdown] slideshow={"slide_type": "subslide"} # ## leggere/scrivere file YAML # - **`yaml.dump(, FILE)`** salva l'oggetto nel file (che deve essere stato già aperto con open) # - **`yaml.safe_load(FILE) -> `** legge l'oggetto dal file (che deve essere stato già aperto con open) # %% slideshow={"slide_type": "subslide"} ## posso decodificare direttamente del testo testo = """ none: [~, null] bool: [true, false, on, off] int: 42 float: 3.14159 list: - LITE - RES_ACID - SUS_DEXT dict: hp: 13 sp: 5 """ yaml.safe_load(testo) # %% slideshow={"slide_type": "subslide"} with open('esempio.yaml', mode='w', encoding='utf8') as F: print(testo, file=F) # !cat esempio.yaml # oppure leggerlo direttamente da file with open('esempio.yaml') as F: EX = yaml.safe_load(F) EX # %% [markdown] slideshow={"slide_type": "subslide"} # # Leggere pagine o file da Internet # Ci sono molte librerie, la più comune è **[requests](https://requests.readthedocs.io/en/latest/user/quickstart/)** # - permette di eseguire richieste sia di tipo **GET** che **POST** # - **GET** classico url che inserite nel browser, volendo con parametri codificati direttamente nell'URL # - **POST** richiesta che una form html fa al server, con tutti i contenuti dei campi della form # - con parametri # - torna un codice di errore/OK # - e decodifica il testo della pagina html o json # # Molto potente, permette anche streaming, sessioni, ... # %%% HTTP requests slideshow={"slide_type": "subslide"} # importo la libreria import requests # leggo la pagina di python.org pagina = requests.get('https://python.org') # lo status code ci dice se tutto è andato bene status = pagina.status_code # se è un testo nell'attributo text trovo il testo decodificato contenuto = pagina.text print('status:\t\t', status, '\nencoding:\t', pagina.encoding,'\n', contenuto[:500]) # %% [markdown] slideshow={"slide_type": "subslide"} # ## le pagine JSON vengono automaticamente decodificate # %%% HTTP requests slideshow={"slide_type": "fragment"} # leggo da internet una pagina JSON pagina_json = requests.get('https://api.github.com') # la decodifica avviene automaticamente col metodo json() risultato = pagina_json.json() risultato # %% [markdown] slideshow={"slide_type": "slide"} # ## Scaricare file binari # - il contenuto "raw" (crudo) è nell'attributo **`content`** della risposta # %%%% leggo una immagine da https://www.python.org/static/img/python-logo@2x.png run_control={"marked": false} slideshow={"slide_type": "fragment"} ## possiamo anche scaricare file binari logo = requests.get('https://www.python.org/static/img/python-logo@2x.png') # posso estrarre dagli headers le dimensioni e il tipo del file () print(logo.headers['Content-Type'], logo.headers['Content-Length']) # %%%% leggo una immagine da https://www.python.org/static/img/python-logo@2x.png run_control={"marked": false} slideshow={"slide_type": "fragment"} # se si tratta di un file di dati (immagine/audio/film ...) # il contenuto lo trovo nell'attributo content dati = logo.content # per salvarlo posso aprire un file in scrittura ('w') e in modo binario ('b') with open('logo.png', mode='wb') as F: F.write(dati) # %%%% leggo una immagine da https://www.python.org/static/img/python-logo@2x.png run_control={"marked": false} slideshow={"slide_type": "fragment"} # !ls -alh 'logo.png' print(dati[:100]) # %% [markdown] slideshow={"slide_type": "fragment"} # Ed ecco il risultato # # ![](logo.png) # %% [markdown] # ### Per mostrare l'immagine in Jupyter senza salvare il file # basta fornire i dati binari alla classe **IPython.display.Image** # %% from IPython.display import Image dati = requests.get('https://i.imgflip.com/5vcovc.jpg').content Image(dati) # %% [markdown] slideshow={"slide_type": "subslide"} # ## Passare parametri ad una GET # Si usa l'argomento **`params`** (un dizionario) # - **`parametri={ chiave: valore, ...}`** # - **`requests.get( , params=parametri )`** # %% slideshow={"slide_type": "subslide"} # Search Google for the requests Python library response = requests.get( 'https://google.com', params={'q': 'python requests'}, ) print(response.text[:1000]) # %% [markdown] slideshow={"slide_type": "subslide"} # ## Passare parametri ad altri metodi HTTP # Si usa l'argomento **`data=`** che contiene i parametri # ```python # requests.post( 'https://httpbin.org/post', data={'key':'value'}) # requests.put( 'https://httpbin.org/put', data={'key':'value'}) # requests.delete( 'https://httpbin.org/delete') # requests.head( 'https://httpbin.org/get') # requests.patch( 'https://httpbin.org/patch', data={'key':'value'}) # requests.options('https://httpbin.org/get') # ```