Llegir i editar fitxers de text de tot tipus (sense format, CSV, JSON, HTML... ) amb Python és ràpid i molt senzill.

Lectura / Escriptura de fitxers amb open/with

Suposem que volem llegir un fitxer de text que conté 20 dones referents en la bioinformàtica.

Aquest és el text:

Nom,Descobriment Famos,Dates
Anna Smith,Desenvolupament d'algoritmes per anàlisi de seqüències d'ADN,1960-
Maria Garcia,Creació de bases de dades genòmiques,1955-
Laura Johnson,Innovacions en anàlisi de xarxes gèniques,1965-
Patricia Brown,Desenvolupament de models predictius en bioinformàtica,1970-
Linda Davis,Contribucions a l'anàlisi de dades d'expressió gènica,1945-2010
Barbara Martinez,Investigació en genòmica del càncer,1968-
Elizabeth Rodriguez,Desenvolupament d'eines bioinformàtiques per l'epigenètica,1973-
Jennifer Hernandez,Estudis sobre variació genètica humana,1980-
Susan Lopez,Desenvolupament de programari per anàlisi de proteïnes,1962-
Margaret Gonzalez,Recerca en metagenòmica,1958-
Dorothy Wilson,Estudis sobre l'evolució molecular,1975-
Sarah Anderson,Desenvolupament de metodologies per seqüenciació de nova generació,1983-
Jessica Thomas,Investigació en biologia computacional estructural,1972-
Karen Taylor,Desenvolupament de bases de dades de proteïnes,1960-
Nancy Moore,Innovacions en bioinformàtica de sistemes,1971-
Betty Jackson,Recerca en biologia de xarxes,1952-
Helen White,Desenvolupament d'eines per anàlisi de RNA-seq,1985-
Sandra Harris,Estudis sobre interaccions proteïna-proteïna,1976-
Donna Martin,Recerca en farmacogenòmica,1967-
Carol Thompson,Desenvolupament de metodologies per l'anàlisi de dades de microarrays,1970-

Sense descarregar cap llibreria externa, podrem llegir el text línia per linia.

file_path = 'bioinformatiques.txt'

with open(file_path, mode='r', encoding="UTF-8") as file:
    # Ometre la primera fila (encapçalats)
    next(file)
    for line in file:
        nom, descobriment, dates = line.strip().split(',')
        print(f"Nom: {nom}, Descobriment: {descobriment}, Dates: {dates}")

Resultat:

$ /bio/python.exe files-path.py | head -3
Nom: Anna Smith, Descobriment: Desenvolupament d'algoritmes per anàlisi de seqüències d'ADN, Dates: 1960-
Nom: Maria Garcia, Descobriment: Creació de bases de dades genòmiques, Dates: 1955-
Nom: Laura Johnson, Descobriment: Innovacions en anàlisi de xarxes gèniques, Dates: 1965

Observacions:

  • En aquest cas suposem que el fitxer està al mateix directori que el codi, si no és així cal indicar correctament la ruta.
  • with --> Paraula reservada per a recórrer tot el fitxer i al final garantir que es tanca correctament (tant si tot va bé com si hi ha exepcions)
  • open --> Mètode per obrir el fitxer per a llegir-lo. El mode r és per indicar que únicament volem llegir el contingut, i l'encoding és important indicar-lo per a què apareguin bé els accents i símbols.
  • next --> La paraula next el fitxer. En aquest cas només hi ha un, però podria llegir tots els fitxers d'una carpeta.
  • for line in file: --> Llegeix linia a linia.
  • line.strip().split(',') -> Ens interessa separar els valors de cada linia separats per comes.

Si volem el pas invers, escriure un nou fitxer de text; ho fem amb aquest codi:

from pathlib import Path

# Llista de noms de dones bioinformàtiques (ficticis)
noms_bioinformatiques = [
    "Anna Smith",
    "Maria Garcia",
    "Laura Johnson",
    "Patricia Brown",
    "Linda Davis",
    "Barbara Martinez",
    "Elizabeth Rodriguez",
    "Jennifer Hernandez"
]
file_path = Path('bioinformatiques2.csv')

with open(file_path, 'w', encoding="UTF-8") as file:
    for nom in noms_bioinformatiques:
        file.write(nom + '\n')
    file.write("Miquel Amorós. CC-BY-SA 4.0\n")

print(f"S'ha generat el fitxer {file_path} amb noms de dones bioinformàtiques.")
print(f"Miquel Amorós. CC-BY-SA 4.0")

Millores: tractament de rutes (Path) i excepcions (try/except).

Aquest codi funciona correctament, però li afegirem dues millores:

  • Path -> Definir millor la ruta del fitxer. Mòdul que ve amb Python.
  • try/except -> Tractament d'excepcions personalitzat.
from pathlib import Path

file_path = Path('bioinformatiques.csv')

try:
    with file_path.open(mode='r', encoding='utf-8') as file:
        # Ometre la primera fila (encapçalats)
        next(file)
        for line in file:
            try:
                nom, descobriment, dates = line.strip().split(',')
                print(f"Nom: {nom}, Descobriment: {descobriment}, Dates: {dates}")
            except ValueError as e:
                print(f"Error al processar la línia: {line.strip()}. Error: {e}")
except FileNotFoundError:
    print(f"El fitxer {file_path} no s'ha trobat.")
except IOError as e:
    print(f"Error al llegir el fitxer {file_path}. Error: {e}")

Fixeu-vos amb com funcionen els blocs per capturar les excepcions.

try:

codi1

codi2

except:

print_excepcio

Si totes les instruccions han funcionat el codi s'executa; si hi ha hagut alguna excepció; Python mira si està definida dins el codi (si és ValueError, FileNotFoundError... ) Si la hem definit mostra el missatge que hem indicat. Altrament mostra el text per defecte.

Provem de forçar una excepció: canviem el nom del fitxer a llegir per un que no existeixi i executem de nou el codi.

file_path = Path('bioinformatics.csv')

Com que no tenim el fitxer, ens surt la excepció que hem programat. Així podem aportar una millor experiència als/les usuaris/es de l'aplicació.

$ /bio/python.exe files-path.py
El fitxer bioinformatics.csv no s'ha trobat.

Exercici lectura fitxers. Crea o escriu un fitxer CSV i fes un programa que serveixi per llegir el seu contingut. Ha de tractar adequadament les excepcions.


Importació de fitxers grans amb llibreries d'anàlisis i visualització de dades.

Si apart de llegir un gran fitxer de dades en forma de taula, volem reorganitzar-lo agrupant totals, generar estadístiques i gràfics el més senzill i eficient és utilitzar la llibreria Pandas.


Descàrrega i lectura de fitxers des d'una URL (urlib3)

Apart de obrir fitxers que ja tenim, ens interessa baixar-nos fitxers que tinguem en un altre servidor. Usarem la llibreria predefinida de Python urllib

Com que és una operació lenta i costosa, només els volem baixar si no ho hem fet encara. Podem controlar si tenim o no el fitxer amb el mòdul gestió del sistema operatiu que porta Pyhton, os.path (que és multiplataforma!)

El que hem fet prèviament és pujar a un repositori propi de Gitlab (amb Github funciona igual) un fitxer.

Aquest fitxer volum_conques_internes_de_Catalunya_2022_2024.csv és una versió filtrada del dataset més actiu de la Generalitat el 2024 en motiu de la sequera, ja que mostra dia a dia com de plens estan els pantans de Catalunya (les conques internes).

Consulta el web service del volum de les conques internes a Catalunya

Proveu el codi; que només llegeix les 30 primeres línies.

import os.path
from datetime import datetime
from urllib.request import urlretrieve

url = "https://gitlab.com/xtec/bio-pandas/-/raw/main/data/volum_conques_internes_de_Catalunya_2022_2024.csv"

data_formatejada = datetime.now().strftime('%d-%m-%Y')
csv_file = f"aigua_conques_internes_{data_formatejada}.csv"

def llegir_primers_30_linies(csv_file):
    try:
        with open(csv_file, mode='r', encoding='utf-8') as file:
            # Llegir les primeres 30 línies
            for i, line in enumerate(file):
                if i >= 30:
                    break
                print(line.strip())
    except FileNotFoundError:
        print(f"El fitxer {csv_file} no s'ha trobat.")
    except Exception as e:
        print(f"S'ha produït un error inesperat: {str(e)}")

# Descarregar el fitxer CSV si no existeix
if not os.path.isfile(csv_file):
    try:
        urlretrieve(url, csv_file)
        print(f"Fitxer {csv_file} descarregat correctament.")
    except Exception as e:
        print(f"No s'ha pogut descarregar el fitxer des de {url}: {str(e)}")

# Cridar la funció per llegir les primeres 30 línies del fitxer CSV
llegir_primers_30_linies(csv_file)

Descàrrega i lectura de fitxers avançada amb la llibreria requests.

La llibreria que hem vist ens soluciona la vida si tenim la sort que podem allotjar les dades en el nostre servidor (en el nostre cas un de Gitlab)

Però en altres ocasions hem de descarregar-nos dates que s'actualitzen sovint, en portals de dades obertes en els que hem de certificar que som persones que volen accedir a les dades per fer-ne un ús confiable.

Sense anar més lluny, per accedir dirèctament al web service original dels embassaments de Catalunya, que si intentem accedir directament al JSON ens retorna un error que no tenim un certificat vàlid.

ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED]
certificate verify failed: unable to get local issuer certificate (_ssl.c:997)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  (...)
urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED]
certificate verify failed: unable to get local issuer certificate (_ssl.c:997)>

Per solucionar aquest problema, consultarem aquestes fonts i les provarem... (pendent)


Lectura de fitxers molt grans amb Generator (yield)

Els algorismes que hem vist fins ara carreguen tot el fitxer a la memòria. Això pot ser lent i ineficient quan són grans, i més en servidors d'aplicacions web.

Per això, podem utilitzar un Generator, que el que fa és anar llegint el fitxer línia per línia.

A continuació, per provar com funciona un Generador i comprovar que aquesta hipotesi és certa (que amb Genrator i yield estalviem temps i recursos), desenvoluparem un benchmark per comparar els dos enfocaments utilitzant un fitxer de 10,000 línies i 12 columnes.

import numpy as np
import time

# Funció per llegir tot el fitxer a la memòria
def read_file_all_at_once(file_path):
    with open(file_path, 'r') as file:
        lines = file.readlines()
        processed_lines = [line.strip() for line in lines]
    return processed_lines

# Funció per llegir el fitxer línia per línia amb generador
def read_file_with_generator(file_path):
    with open(file_path, 'r') as file:
        for line in file:
            yield line.strip()

# Generar fitxer de prova
num_lines = 12000
num_columns = 50
data = np.random.rand(num_lines, num_columns)

file_path = 'large_file.txt'
with open(file_path, 'w') as file:
    for row in data:
        file.write(','.join(map(str, row)) + '\n')

# Benchmarking sense generadors
start_time = time.time()
all_lines = read_file_all_at_once(file_path)
end_time = time.time()
print("Temps de lectura sense generadors:", end_time - start_time)

# Benchmarking amb generadors
start_time = time.time()
for line in read_file_with_generator(file_path):
    pass  # Processa cada línia aquí
end_time = time.time()
print("Temps de lectura amb generadors:", end_time - start_time)

Resultats del Benchmarking:

Temps de lectura sense generadors: 0.05058765411376953

Temps de lectura amb generadors: 0.017226219177246094

Com veiem, el temps de lectura amb generadors és inferior, per tant és més eficient a nivell de temps.

Com es crea el Generador ?

La gràcia de la funció read_file_with_generator és que en comptes de tenir el return té la paraula clau yield.

La paraula clau yield és la clau que converteix aquesta funció en un generador. En lloc de retornar un valor i sortir de la funció (com faria return), yield retorna una línia processada (sense espais en blanc al principi i al final) i suspèn l'execució de la funció. La propera vegada que es cridi next() sobre l'iterador retornat per aquesta funció, la execució es reprèn just després del yield.

--PENDENT A PARTIR D'AQUÍ--

Referències a consultar:

La 1, 2 i la 7 ja estan!

  1. https://www.udacity.com/blog/2021/09/getting-started-with-try-except-in-python.html
  2. https://realpython.com/python-download-file-from-url/
  3. https://medium.com/@technige/what-does-requests-offer-over-urllib3-in-2022-e6a38d9273d9
  4. https://stackabuse.com/bytes/how-to-unzip-a-gz-file-using-python/
  5. https://bito.ai/resources/unzip-gz-file-python-python-explained/
  6. https://pywombat.com/articles/shutil-python
  7. https://www.udacity.com/blog/2021/09/getting-started-with-try-except-in-python.html

Asyncio: