# ---
# jupyter:
# jupytext:
# formats: ipynb,py:percent
# text_representation:
# extension: .py
# format_name: percent
# format_version: '1.3'
# jupytext_version: 1.14.0
# kernelspec:
# display_name: Python 3 (ipykernel)
# language: python
# name: python3
# ---
# %% [markdown] toc=true slideshow={"slide_type": "notes"}
#
Table of Contents
#
# %% [markdown] slideshow={"slide_type": "slide"}
# # Fondamenti di Programmazione
#
#
# **Andrea Sterbini**
#
# lezione 11 - 10 novembre 2022
# %% [markdown] slideshow={"slide_type": "subslide"}
# # RECAP: Files
#
# - **`with open(, mode=, encoding='utf8') as F:
# ...`**
# - **`F.read()`** legge tutto il contenuto
# - **`F.readline()`** legge UNA riga (compreso **`'\n'`**)
# - **`F.readlines()`** legge TUTTE le righe (compresi **`'\n'`**)
# - **`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"}
# # Ricerca delle parole più importanti in un gruppo di documenti
# - per ogni documento
# - carichiamo le parole
# - le contiamo
# - ne calcoliamo la frequenza percentuale
# %% [markdown] slideshow={"slide_type": "subslide"}
# ## Ricerca del file più importante dato un gruppo di parole
# Le parole più frequenti potrebbero essere troppo comuni e poco significative
# (con poco contenuto semantico)
#
# Quindi una parola sarà "interessante" se **appare spesso in un file** ma **appare in pochi file**
#
# Vedi https://en.wikipedia.org/wiki/tf-idf
# %% [markdown] slideshow={"slide_type": "subslide"}
# ## Ci serve la frequenza di una parola in ciascun file (tf = term frequency)
# - 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 files con il loro encoding, ottenuto unzippando files.zip
files = {
'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, encoding):
# apro il file 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
nonalfa = [ c for c in caratteri if not c.isalpha() ]
# li rimpiazzo con spazio
for c in nonalfa:
text = text.replace(c, ' ')
# uso split sul testo in cui ho eliminato tutti i non-apha
return text.split()
# esempio: parole del libro Alice nel paese delle meraviglie
parole = estrai_parole('files/alice_it.txt', 'latin')
### come usare meno memoria? lavorando riga per riga
parole[:30]
# %%%% - mi serve l'importanza di una parola in un testo slideshow={"slide_type": "subslide"}
# per contare quante sono le parole
def conta_parole(parole):
# 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(list(conteggi.items())[:20])
# le 50 parole più presenti in Alice sono:
più_presenti = sorted(conteggi, reverse=True,
key=lambda K: conteggi[K])[:50]
list(più_presenti)
# %%%% 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 = {} # conteggio delle parole nei file: frequenze[name][parola] -> % di parole
# conto le frequenze file per 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"}
# ## e l'inverso della presenza della parola nei file (idf = inverse document frequency)
# %%%% 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 = {}
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
list(presenze.items())[:30]
# %%%% - 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 = { 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 file = tf_i * slideshow={"slide_type": "subslide"}
# e finalmente posso vedere quale file "pesa di più" rispetto alle parole della query
# date le parole di una query
# per ciascun file
# ne conto la frequenza nel file
# la moltiplico per idf
# sommando ottendo il "peso" del file per quella query
query = [ 'turtle', 'alice']
pesi = {}
for file in frequenze:
peso = 0
for p in query:
# aggiungo il peso della parola (0 se non presente)
peso += frequenze[file].get(p,0) * idf[p]
pesi[file] = peso
print(f"{file:30} {peso:00.3}")
# ordino i file per peso decrescente
sorted(pesi.items(), reverse=True, key=lambda coppia: coppia[1])
# %% LUNEDI': json, http requests e analisi del testo e ricerca [markdown] slideshow={"slide_type": "slide"}
# # Altri tipi di file: [JSON](https://docs.python.org/3/library/json.html)
# File di testo con sintassi semplificata per rappresentare:
# - dizionari
# - liste
# - interi, stringhe, float, bool, None
#
# Molto usati nelle applicazioni WEB
# %% slideshow={"slide_type": "subslide"}
## Esempio
import json
with open('api.github.com.json') as F:
api = json.load(F)
# !cat api.github.com.json
# %% LUNEDI': json, http requests e analisi del testo e ricerca [markdown] slideshow={"slide_type": "subslide"}
# ## Attenzione:
# - NON si possono inserire commenti
# - NO virgola aggiuntiva in fondo a lista o dizionario
# - SOLO chiavi semplici (NO tuple, SI int, float, str, none, bool)
# - SOLO doppi apici **`"`** (NO singoli apici)
# %% [markdown] slideshow={"slide_type": "subslide"}
# ## Abbastanza facili da usare in python
# Conversione automatica per alcuni tipi di dati
# - **`json.load() -> dict | list | tipo_base`**
# - **`json.dump(, )`**
#
# Personalizzabile anche per altri tipi di oggetti (non ovvio)
# %% slideshow={"slide_type": "subslide"}
# posso anche leggere da una stringa in formato JSON
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.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 aperto con open
# - **`yaml.safe_load(FILE) -> `** legge l'oggetto dal file 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 sia **GET** che **POST**
# - 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
status, pagina.encoding, contenuto[:100]
# %% [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" è 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'])
# se si tratta di un file di dati (immagine/audio/film ...)
# il contenuto lo trovo nell'attributo content
dati = logo.content
# per salvarlo devo aprire il file in scrittura ('w') e in modo binario ('b')
with open('logo.png', mode='wb') as F:
F.write(dati)
# %% [markdown] slideshow={"slide_type": "fragment"}
# 
# %% [markdown] slideshow={"slide_type": "subslide"}
# ## Passare parametri ad una GET
# Si usa l'argomento **`params`**
# - **`parametri={ chiave: valore, ...}`**
# - **`requests.get( , params=parametri )`**
# %% slideshow={"slide_type": "subslide"}
# Search GitHub's repositories for requests
response = requests.get(
#'https://api.github.com/search/repositories',
'https://google.com',
params={'q': 'requests+language:python'},
)
# response.json()
response.text[:1000]
# %% [markdown] slideshow={"slide_type": "subslide"}
# ## Passare parametri ad altri metodi HTTP
# Si usa l'argomento **`data=`**
# ```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')
# ```