Fluxos i accés a fitxers
Quan es programa, sovint es necessita treballar amb dades que es volen guardar permanentment o compartir entre diferents aplicacions. Els fitxers són una forma de magatzem extern que permet emmagatzemar informació de manera duradora, independentment de la memòria del programa que s’està executant.
En aquesta lliçó, veureu com Python proporciona eines senzilles per manipular fitxers de text. Aprendreu a llegir, escriure i gestionar informació dins dels fitxers, tasques essencials per a la majoria d’aplicacions i projectes.
Fluxos de fitxers, lectura i escriptura
Quan es treballa amb fitxers en programació, és necessari entendre què és un flux de fitxer i com funciona. Els fluxos de fitxer faciliten la comunicació entre el programa i el sistema d’emmagatzematge (el disc dur o altre dispositiu), i ajuden a controlar com la informació viatja entre l’ordinador i el fitxer. Veureu que, gràcies als fluxos, és possible llegir i escriure fitxers amb molta flexibilitat i control.
L’ús d’aquests fluxos i els seus mètodes us ajudarà a llegir i escriure fitxers de manera senzilla.
Introducció als fluxos i tipus de fitxer
Un flux de fitxer és un canal de comunicació que permet transferir dades entre un programa i un fitxer en una direcció concreta, sigui per llegir o per escriure informació. En altres paraules, és el camí a través del qual la informació flueix des del fitxer cap al programa (en mode de lectura) o des del programa cap al fitxer (en mode d’escriptura).
Això vol dir que, quan s’obre un fitxer, s’estableix un canal o flux de comunicació. Penseu en un flux com en un tub per on passa l’aigua: l’aigua pot anar en una direcció o en l’altra, però cal saber si s’està entrant o sortint de la canonada.
Hi ha dos tipus de fluxos principals:
- Flux de lectura: les dades flueixen del fitxer cap al programa, és a dir, el programa llegeix informació emmagatzemada en el fitxer.
- Flux d’escriptura: les dades flueixen del programa cap al fitxer, cosa que permet afegir o modificar informació dins del fitxer.
Quan es crea un flux, també es pot especificar si es vol accedir a tot el fitxer sencer o si només es vol llegir o escriure una part del seu contingut. Aquesta flexibilitat fa que es pugui gestionar la informació amb més eficiència.
Un altre aspecte important a considerar són els diferents tipus de fitxers amb els quals es pot treballar. Aquesta lliçó se centrarà principalment en els fitxers de text, però cal entendre breument la diferència entre els fitxers de text i els fitxers binaris.
Fitxers de Text
Els fitxers de text són fitxers on la informació es guarda en format de caràcters llegibles (lletres, números, símbols) en un format com ara UTF-8. Quan s’obre un fitxer de text una aplicació s’encarrega de transformar els bytes a caràcters i es veuen frases, paraules o altres tipus de dades escrites.
En Unix no és necessari que els fitxers s’associïn a una extensió. Un fitxer de text que coneixereu bé és el /etc/passwd, en el que cada línia representa un usuari i els camps estan separats pel caràcter :.
Exemples de fitxers de text són els arxius .txt, .csv (valors separats per comes) o .json (format de dades estructurades).
Fitxers binaris
Els fitxers binaris contenen dades emmagatzemades com a bits i bytes (uns i zeros), que no són directament llegibles per una persona. Aquests fitxers necessiten un programa o aplicació específica per ser interpretats correctament com a imatges i vídeos, o poden ser un magatzem de dades estructurades d’una manera concreta i que s’han de llegir amb unes instruccions precises.
Exemples de fitxers binaris són els fitxers d’imatge (.jpg, .png), fitxers de vídeo (.mp4) o fins i tot fitxers d’aplicació executables (.exe).
Tipus d'accés als fluxos de fitxer
Tot i que qualsevol fitxer digital es compon de bits (zeros i uns), el flux i el tipus d’accés que s’utilitzen per treballar-hi han de ser diferents segons el tipus de fitxer: de text o binari.
- Fitxers de text: l’accés seqüencial és l’estàndard perquè s’espera que es llegeixin línies completes, i Python interpreta automàticament els caràcters especials (com els salts de línia), ja que es llegeixen o s’escriuen dades en format llegible per humans (caràcters, línies completes). Així, el flux avança per tot el fitxer de forma contínua. Es pot entendre gràficament com si el flux fos una canonada que goteja caràcter a caràcter. O potser seria millor indicar línia a línia, ja que els mètodes més utilitzats trossegen el fitxer original a partir del caràcter de salt de línia.
- Fitxers binaris: contenen dades en format de bytes, sovint és necessari l’accés directe per poder anar a posicions específiques. Per això, el flux pot ser manipulat per llegir o escriure en ubicacions concretes, sense necessitat de recórrer seqüencialment tot el fitxer. Aquesta flexibilitat fa el flux molt útil per fitxers grans i estructurats en blocs, com ara imatges o vídeos. En aquest cas ja no funciona la imatge gràfica de la canonada, ja que es pot accedir directament al caràcter que es vol. La imatge que més s’acosta és la de considerar un fitxer com una taula de dades, o caselles, i l’accés directe com la possibilitat de dirigir-se a una casella directament sense haver de passar per les anteriors.
Tipus d’accés a un fitxer
El flux de fitxer, com a canal de comunicació entre el programa i el fitxer, no canvia en essència, però s’adapta segons les necessitats de cada tipus. Aquest flux pot ser seqüencial (llegint les dades en ordre des del principi fins al final) o directe (accedint a ubicacions específiques del fitxer).
Aquesta lliçó se centrarà en fitxers de text i en el tipus d’accés seqüencial.
Obrir i tancar fitxers
Quan es treballa amb fitxers en Python, és fonamental saber com obrir-los i tancar-los correctament. Aquest procés estableix el flux de dades entre el programa i el fitxer, per la qual cosa es pot llegir o escriure en aquest amb seguretat.
Obertura de Fitxers: la funció ''open()''
Sintaxi de la funció open():
Els dos arguments han de ser de tipus
fitxer = open("nom_fitxer.txt", "r")
str.
Per accedir a un fitxer en Python, s’utilitza la funció integrada open(). Aquesta funció pren almenys dos arguments:
- Nom del fitxer: la ruta o nom del fitxer que es vol obrir.
- Mode d’obertura: defineix com s’accedirà al fitxer.
El mode d’obertura és una cadena que indica com s’obrirà el fitxer. En altres paraules, és el que decideix si l’accés es farà com si fos una canonada (mode seqüencial), o un taulell de caselles (mode directe). I si és per a rebre dades (mode lectura), o per escriure-les (mode escriptura).
Principals modes d’obertura de fitxers:
- “r”: mode de lectura (
read). Només permet llegir el fitxer; si el fitxer no existeix, es genera un error. - “w”: mode d’escriptura (
write). Si el fitxer no existeix, es crea; si ja existeix, el contingut es sobreescriu. - “a”: mode d’afegir (
append). Permet escriure al final del fitxer sense sobreescriure el contingut existent. - “b”: mode binari. Es pot combinar amb altres modes (
“rb”,“wb”, etc.) per treballar amb fitxers en format de bytes en lloc de text.
El tipus de dada FILE
Quan s’obre un fitxer amb open(), aquest retorna un objecte FILE que conté la informació del fitxer accedit i els recursos necessaris per manipular-lo.
Aquest objecte inclou les següents característiques:
- Ruta del fitxer: l’objecte
FILEguarda la informació sobre la ubicació del fitxer, sigui en el sistema local o en una ubicació específica en una unitat. - Mode d’obertura: en obrir el fitxer, se li indica un mode (“r”, “w”, “a”, etc.) que defineix com es treballarà amb el fitxer (lectura, escriptura, afegir…).
- Estat del fitxer: el fitxer es manté obert mentre s’estigui fent servir l’objecte
FILEi es pot utilitzar per dur a terme diferents operacions. És important recordar que, mentre el fitxer estigui obert, ocuparà recursos del sistema.
Ús de directori actiu
Per a aquest curs, es farà servir sempre el directori actiu en treballar amb fitxers. Aquest enfocament ajuda a evitar complicacions amb les rutes dels fitxers, especialment si l’aplicació es trasllada a un altre sistema de fitxers o a un entorn diferent. En comptes d’especificar una ruta completa (com ara /home/user/docs), que pot variar segons el sistema operatiu, treballar amb el directori actiu permet escriure i accedir a fitxers en la ubicació des de la qual s’està executant el programa.
Això facilita la portabilitat del codi, ja que quan el programa s’executi en un altre sistema o directori, els fitxers es crearan o s’hi accedirà directament des d’aquest directori actiu, sense necessitat de canviar el codi per adaptar les rutes de fitxers.
Tancar fitxers: el mètode ''close()'' de l'objecte ''FILE''
Un cop s’ha acabat de treballar amb un flux de fitxer, és crucial tancar-lo amb el mètode close(). Això és important per diverses raons:
- Alliberació de recursos: cada fitxer obert consumeix recursos del sistema, com la memòria i espai de descriptors de fitxers. Si no es tanquen els fitxers, aquests recursos continuaran ocupats, la qual cosa pot afectar el rendiment i, en alguns casos, fins i tot esgotar els descriptors disponibles.
- Seguretat de les dades: quan es tanca un fitxer, el sistema s’assegura que totes les dades s’hagin escrit correctament al disc. Això és especialment important en fitxers oberts en mode d’escriptura, ja que tancar-los evita possibles pèrdues de dades en cas de talls inesperats de corrent o d’errors del programa.
- Evitar errors d’accés: mantenir un fitxer obert després d’haver acabat de treballar-hi pot causar problemes si es vol obrir el fitxer de nou, sigui en el mateix programa o en un altre procés.
Exemple d'obertura i tancament
El següent codi mostra com obrir un arxiu en mode lectura, i com tancar-ho. Aquest serà l’esquema bàsic de qualsevol programa que treballi amb arxius de text.
# Obrir el fitxer en mode de lectura fitxer = open("nom_fitxer.txt", "r") # llegir les dades # Quan hem acabat de treballar amb el fitxer, es tanca fitxer.close() # Processar les dades
Lectura de fitxer
Un cop obert un fitxer en mode de lectura (“r”), Python ofereix diverses maneres d’obtenir-ne el contingut. Aquestes tècniques permeten treballar amb les dades d’acord amb les necessitats del programa, ja sigui llegint tot el contingut de cop, línia per línia o fins a un nombre determinat de caràcters.
Els fitxers de text estan organitzats en línies perquè són fàcils de processar tant per als programes com per als humans. En obrir un fitxer de text, Python sap reconèixer aquests salts de línia (\n) i considera cada línia com un bloc de dades que es pot llegir i processar de forma independent. Aquesta estructura ajuda a organitzar la informació de manera lògica i permet llegir dades en parts fàcilment manejables.
El mode de lectura “r” produeix un error si el fitxer del qual es vol realitzar la lectura no existeix.
En els exemples següents s’utilitzarà el fitxer exemple.txt, que conté els caràcters següents:
hola adeu Andreu
El fitxer conté 2 línies, acabades les dues en un salt de línia (després de la paraula Andreu s’ha afegit el caràcter ‘\n’).
Els fitxers com a iterables
Quan es parla d’un fitxer com un objecte iterable, significa que es pot llegir progressivament, element a element, sense haver de carregar tot el contingut de cop. En el cas d’un fitxer de text, aquests “elements” són les línies. Això vol dir que quan utilitzeu un bucle for sobre un fitxer, cada iteració del bucle obté una línia del fitxer, en ordre, fins que s’arriba al final.
La lectura de fitxers amb un bucle for és una de les maneres més naturals i eficients de treballar amb fitxers de text en Python. Aquesta forma de lectura té diversos avantatges:
- Eficiència en memòria: el bucle
forllegeix cada línia una a una i no carrega tot el fitxer a la memòria, cosa que el fa ideal per fitxers grans.
- Simplicitat: la sintaxi és simple i fàcil de llegir, fent que el codi sigui més comprensible.
El següent codi és un exemple de lectura d’un fitxer amb el bucle for:
El mètode strip() és un mètode de cadenes que treu els caràcters problemàtics del final de la cadena, com el salt de línia. La línia es llegeix sencera, inclòs el intro del final (no com la funció input()). L’ús de strip() neteja aquest últim caràcter i permet utilitzar la línia com si s’hagués llegit utilitzant input().
Sense l’ús de .strip(), cada línia impresa tindria un salt de línia addicional.
fitxer = open("exemple.txt", "r") for linia in fitxer: print(linia.strip()) # .strip() elimina el salt de línia al final fitxer.close()
Aquest és el resultat de l’execució del programa (si s’executa en un directori en el qual hi ha el programa i el fitxer exemple.txt)
$ python3 metode_read.py hola adeu Andreu
Mètodes de lectura
L’objecte FILE de Python proporciona diferents mètodes per llegir el contingut d’un fitxer, segons el volum de dades o la manera en què es vol gestionar la informació.
Mètode read(): llegir tot el contingut
Aquest mètode llegeix tot el contingut del fitxer de cop i retorna una cadena amb les dades.
En el codi següent es crea un flux de fitxer que llegeix tot el fitxer de cop i el desa a la variable contingut:
El mètode read() només està indicat quan se sap que els fitxers a llegir són petits, com podria ser el cas d’un fitxer de configuració d’alguna aplicació.
fitxer = open("exemple.txt", "r") contingut = fitxer.read() fitxer.close() print(contingut)
Aquest mètode permet llegir tot el fitxer de cop. El que en principi sembla un avantatge per la seva simplicitat no ho és: els fitxers de text poden arribar a ser molt grans i col·lapsar la memòria.
El mètode readline(): llegir una línia
Si només es necessita una línia (o no cal llegir el fitxer sencer), readline() llegeix la següent línia del fitxer.
fitxer = open("exemple.txt", "r") linia = fitxer.readline() fitxer.close() print(linia) print(linia.strip())
El resultat de l’execució (si es troben el fitxer i el programa en el directori actiu), és el següent:
$ python3 metode_readline.py hola hola
Aquest exemple també mostra la feina del mètode strip(): si no es neteja la línia, en combinar-la amb la funció print() apareixen dos salts de línia, el que ja duia la mateixa línia i el que afegeix print(). Per això print(linia) imprimeix després de la primera línia (“hola”) un salt de línia extra.
El mètode readlines(): llegir totes les línies com a llista
El mètode readlines() llegeix totes les línies i les retorna com una llista de cadenes, on cada element és una línia del fitxer.
Fixeu-vos en el codi següent
fitxer = open("exemple.txt", "r") linies = fitxer.readlines() fitxer.close() print(linies)
I en el resultat de l’execució
$ python3 metode_readline.py ['hola\n', 'adeu Andreu\n']
Aquest mètode, com en el cas de readline(), incorpora els salts de línia a la cadena.
S’ha d’utilitzar amb les mateixes restriccions que read(), donat que es carrega tota la informació del fitxer en memòria de cop.
Tancar el fitxer de seguida
Reviseu els exemples anteriors i adoneu-vos que el flux de fitxer es tanca sempre, amb el mètode close(), abans de processar les dades, ni que l’únic procés sigui mostrar-les per la sortida estàndard.
És molt important mantenir els fitxers oberts el mínim temps possible per minimitzar els errors que es puguin produir durant la lectura de dades. Encara que el procés a fer sigui tan senzill com mostrar les dades, cal tancar el flux del fitxer després de llegir el seu contingut.
Quan es treballa amb fitxers, convé mantenir el fitxer obert només el temps necessari per accedir a les dades i, a continuació, tancar-lo immediatament. És una pràctica recomanada llegir les dades que es necessiten i, un cop obtingudes, fer el processament de la informació amb el fitxer ja tancat. Això té diversos avantatges:
- Reducció de l’ús de recursos: mantenir un fitxer obert ocupa recursos del sistema, com memòria i descriptors de fitxers. En tancar-lo ràpidament, alliberem aquests recursos i reduïm la càrrega del sistema, especialment si es treballa amb molts fitxers o en aplicacions grans.
- Millor seguretat i consistència de dades: quan un fitxer es tanca, el sistema s’assegura que les dades es guarden correctament. Si el fitxer es manté obert durant un llarg període, hi ha un major risc de pèrdua de dades en cas d’errors del programa o talls d’alimentació.
- Evitar bloquejos o errors d’accés: altres programes o processos podrien necessitar accedir al mateix fitxer. Si es manté obert innecessàriament, podria bloquejar altres processos o provocar errors d’accés, especialment en entorns compartits.
Aquest enfocament permet que el codi sigui més eficient, segur i fàcil de gestionar.
Escriptura de fitxers
Quan es treballa amb escriptura de fitxers en Python, es pot obrir un fitxer en diferents maneres (modes), depenent de si es vol escriure noves dades o afegir-les a un fitxer existent.
Mode “w” (escriptura)
Aquest mode crea un fitxer nou si el fitxer a obrir no existeix. Si ja existeix sobreescriu el fitxer existent. Cada vegada que s’obre el fitxer en mode “w”, es buida el seu contingut anterior.
En l’exemple següent s’utilitzarà el fitxer exemple1.txt, que conté els caràcters següents:
Andreu
El fitxer conté 1 línia, acabada en un salt de línia (després de la paraula Andreu s’ha afegit el caràcter ‘\n’).
Fixeu-vos en el codi següent del programa metode_write.py que escriu un fitxer exemple1.txt.
fitxer = open("exemple1.txt", "w") fitxer.write('Se sobreescriu el fitxer\n') fitxer.close()
Aquest és el resultat de la seva execució.
$ python3 metode_write.py videoioc@debian-xtec:~/Documents/programacio/UF3$ cat exemple1.txt Se sobreescriu el fitxer
En aquest cas, podeu comprovar com “Andreu” ha desaparegut del fitxer exemple1.txt.
Mode “a” (afegir)
Aquest mode també obre el fitxer per escriptura, però, en aquest cas, conserva el contingut existent i hi afegeix noves dades al final. És útil quan es volen anar afegint registres o línies sense perdre el que hi havia al fitxer
fitxer = open("exemple.txt", "a") fitxer.write('adeu\n') fitxer.close()
I el resultat de l’execució amb el fitxer exemple original
$ python3 metode_write.py $ cat exemple.txt hola adeu Andreu adeu
Els fitxers de text solen estar estructurats en línies, però la decisió de l’estructura del fitxer resultant és del programador. A continuació, podeu veure en dos exemples com usar el mode “w” d’escriptura i com usar el mode “a” d’afegir. El següent codi mostra com es crea un fitxer nou (o es sobreescriu sa ja existia):
dades = ["Línia 1", "Línia 2", "Línia 3"] fitxer = open("sobreescrit.txt", "w") for linia in dades: fitxer.write(linia + "\n") # Afegim \n per separar cada línia fitxer.close()
Amb el següent resultat d’execució:
$ python3 exemple1.py $ cat sobreescrit.txt Línia 1 Línia 2 Línia 3
El següent codi mostra com es fa per afegir dades a un fitxer ja existent:
noves_dades = ["Línia 4", "Línia 5"] fitxer = open("sobreescrit.txt", "a") for linia in noves_dades: fitxer.write(linia + "\n") fitxer.close()
Amb el següent resultat d’execució, entenent que el fitxer sobreescrit és el resultant de l’execució anterior:
$ python3 exemple2.py $ cat sobreescrit.txt Línia 1 Línia 2 Línia 3 Línia 4 Línia 5
Atenció, el mode d’escriptura “a”, permet afegir el text que es desitgi al final d’un fitxer existent. En cas que el fitxer no existeixi, se’n crea un de nou amb la informació aportada.
Gestió d'errors
Quan es treballa amb fitxers, poden aparèixer diversos errors que, si no es gestionen adequadament, poden provocar que el programa falli de forma inesperada. En Python, la lectura i escriptura de fitxers es pot gestionar amb l’estructura try-except-finally per evitar aquests problemes i assegurar que el programa continua funcionant, fins i tot si es produeix un error.
Els errors comuns en la lectura i escriptura de fitxers són els següents:
- Fitxer inexistent: si s’intenta obrir un fitxer per llegir (“r”) que no existeix, Python generarà un error del tipus
FileNotFoundError. - Permisos insuficients: si el programa intenta escriure en un fitxer sense permisos suficients o en un directori protegit, es produirà un error de tipus
PermissionError. - Fitxer obert en el mode equivocat: intentar llegir en un fitxer obert en mode escriptura (“w”) o escriure en un fitxer obert en mode lectura (“r”) genera un error
UnsupportedOperation. - Fitxer ja tancat: si es vol llegir o escriure en un fitxer després de tancar-lo, Python mostrarà un error
ValueError. - Errors diversos d’entrada/sortida: l’operació de trasllat de dades entre un programa i un fitxer sempre pot provocar errors diversos de dades (que el disc dur estigui malmès, que es retiri el llapis de memòria que conté el fitxer…). Tot aquest tipus d’errors, i els anteriors es troben compresos en l’error genèric
IOError.
Ús de ''try-except-finally'' per gestionar errors d'entrada-sortida
El bloc try-except-finally en Python és una estructura per gestionar errors (excepcions) en el codi. El seu ús permet intentar executar un bloc de codi (try), capturar i gestionar qualsevol error que s’hi produeixi (except) i, finalment, executar un conjunt d’instruccions independentment de si s’ha produït un error o no (finally).
Les clàusules indiquen el següent:
try: conté el codi que pot provocar un error. Si es produeix una excepció en aquest bloc, es deixa d’executar i es passa al blocexcept.except: aquest bloc s’executa només si es produeix una excepció en el bloctry. Permet gestionar errors específics (comFileNotFoundError) o genèrics (si no s’especifica el tipus es recull qualsevolExceptionque s’hagi produït.finally: aquest bloc s’executa sempre, tant si es produeix una excepció com si no. S’utilitza per assegurar que es compleixin accions crítiques, com tancar un fitxer o alliberar recursos.
Gestió de FileNotFoundError
El següent codi mostra com gestionar un error de tipus FileNotFoundError.
try: fitxer = open("inexistent.txt", "r") contingut = fitxer.read() fitxer.close() except FileNotFoundError: print("Error: El fitxer no existeix.")
Aquí, si el fitxer no es troba, Python executa el bloc except, mostrant un missatge d’error en lloc de fallar.
Si s’executa aquest programa en un directori en què no hi hagi cap fitxer amb el nom inexistent.txt, s’aconsegueix que el programa acabi de manera neta mostrant un missatge d’error.
$ python3 exemple3.py Error: El fitxer no existeix.
Consideracions amb l’ús del try-except amb els fitxers per a aquest curs:
- Dins del
trys’incorporaran les accions perilloses quan es treballa en fitxers, com són l’obertura i la lectura o escriptura de dades. - Serà necessari l’ús de dos
tryniuats, el primer per a comprovar l’existència del fitxer i el segon per a recollir qualsevol error d’entrada/sortida. - En l’exterior es tractarà el cas del
FileNotFoundError. Si el fitxer no existeix es recollirà l’error i s’acabarà el programa. - En l’interior es tractaran tots els altres problemes de lectura. Com que el fitxer estarà obert, caldrà tancar-lo sempre i, per això, serà necessari l’ús de la clàusula
finally, per assegurar-ne el tancament. - Els errors d’entrada i sortida es consideren fatals per al programa, ja que impedeixen la seva execució. És per això que es tractarà el
try-exceptcom a un control d’errors i es permet l’ús de la funcióexit(). El control d’errors no ha de seguir els preceptes de la programació estructurada. - El processament de les dades es farà, sempre que sigui possible, després d’haver tancat el fitxer.
Gestió de IOErrors en els nostres programes
El següent codi mostra com realitzar el control d’errors d’entrada i sortida tenint en compte errors de tipus IOError. És un model pels programes en els quals feu ús dels fitxers.
try: #obertura fitxer = open("exemple.txt", "r") try: #lectura contingut = fitxer.read() except IOError as e: print(f"S'ha produït un error inesperat: {e}") exit(1) finally: fitxer.close() except FileNotFoundError: print("Error: No s'ha trobat el fitxer.") exit(1) #processament de les dades print(contingut)
Com ja sabeu, el control d’errors s’ha d’afegir una vegada el programa ja funciona. En el cas dels try-except aquesta premissa esdevé encara més important, donat que poden emmascarar altres tipus d’errors.
En general, se us demanarà el programa sense el control d’errors, i només quan s’especifiqui, caldrà que entregueu el programa complet aplicant aquesta “recepta” per als fitxers.
Breu menció a les excepcions
Quan es produeix un error en Python, el programa genera una excepció. Si aquesta no es gestiona, el programa es tanca automàticament.
Utilitzar try-except permet gestionar aquestes excepcions per mantenir el programa en funcionament. Les excepcions són útils perquè ofereixen informació sobre què ha anat malament i permeten anticipar i solucionar aquests problemes de manera més controlada.
Aquestes tècniques no només eviten errors inesperats sinó que també ofereixen una experiència d’usuari més robusta i professional.
En aquest curs, però, només s’esmenten de manera breu perquè sapigueu que són una eina molt valuosa i molt potent per a la gestió dels errors (no només d’entrada i sortida).
Es considera necessari utilitzar aquesta estructura sempre que es treballa amb fitxers, tot i que per elaborar un programa, el control dels errors només s’ha d’afegir una vegada s’ha verificat que el programa funciona en els casos favorables del joc de proves.
stdin, stdout i stderr
En Python, stdin (entrada estàndard), stdout (sortida estàndard) i stderr (sortida d’errors estàndard) es gestionen com a fluxos de fitxer. Això vol dir que es poden llegir dades des de stdin o escriure-les a stdout i stderr de manera similar a com es treballaria amb un fitxer de text.
stdin, stdout i stderr són els canals d’entrada i sortida estàndard d’un sistema. Per defecte, quan un usuari escriu al teclat, el que escriu l’usuari el sistema ho rep a stdin. Quan es mostren missatges com a resultat d’algun procés, el sistema ho envia a stdout que, per defecte, apunta a pantalla. I el sistema envia els errors a stderr que generalment per defecte apunta a pantalla, o a un arxiu d’errors.
Lectura amb stdin
stdin representa el flux d’entrada estàndard, que és on el programa rep informació, generalment des del teclat o d’una altra font d’entrada. La funció input() utilitza stdin, però es pot accedir a aquest flux directament amb sys.stdin per a operacions de lectura més personalitzades, com llegir múltiples línies o processar dades contínuament.
Tots els fitxers acaben amb un caràcter que s’anomena EOF (que significa End Of File).
Per indicar a stdin que es finalitza l’entrada de dades, heu d’escriure la combinació de tecles Crtl+d, que fa la funció del EOF
Si executeu el següent fitxer, podeu anar entrant dades fins que introduïu el EOF (Ctrl+d).
import sys for linia in sys.stdin: print(f"línia: {linia.strip()}")
En la següent execució s’ha introduït les línies hola i adeu.
$ python3 exemple4.py hola línia: hola adeu línia: adeu
Escriptura amb ''stdout''
stdout és el flux de sortida estàndard, que envia informació a la pantalla de forma predeterminada. La funció print() envia les dades a stdout i afegeix un salt de línia al final, però també es pot accedir a aquest flux directament amb sys.stdout, cosa que permet un control més fi sobre l’escriptura, com ara afegir o ometre el salt de línia automàtic.
En el següent exemple s’utilitza sys.stdout.write() per escriure directament al flux de sortida, afegint un salt de línia manualment. Això és especialment útil quan es requereix una sortida personalitzada.
import sys missatge = "Aquest missatge es mostrarà per stdout." sys.stdout.write(missatge + "\n")
Escriptura amb stderr
stderr és un flux de sortida reservat per als missatges d’error. En lloc d’imprimir errors o advertències a stdout, es poden enviar a stderr per a distingir-los de la sortida normal. Això facilita la separació de la informació general dels errors, especialment en aplicacions complexes o en sistemes que processen la sortida de manera automàtica.
Tots tres fluxos (sys.stdin, sys.stdout i sys.stderr) funcionen com a fitxers temporals que estan sempre oberts per a l’entrada i la sortida. Això permet llegir o escriure dades contínuament, com si es tractés d’un fitxer de text, i resulta especialment útil en aplicacions que processen dades en temps real o en entorns que utilitzen pipes (|) per passar dades entre programes.
Programació modular, SRP i fitxers
Un dels conceptes clau en programació és el de modularitat, és a dir, dividir el codi en petites parts o mòduls (com ara funcions) que es poden entendre, provar i mantenir de forma independent. Aquest enfocament facilita la programació i fa que el codi sigui més net i organitzat.
En programació modular, se segueix el principi de responsabilitat única. Aquest principi estableix que cada funció o mòdul hauria de tenir una sola responsabilitat o objectiu. Per exemple:
- Una funció que només llegeixi un fitxer.
- Una funció separada que només mostri les dades.
Aquest principi ajuda a construir codi reutilitzable i fàcil de modificar, perquè qualsevol canvi en una funció no afectarà altres parts del programa.
Gestió de la lectura de fitxers amb funcions
Fins ara, s’ha explicat que la gestió de les dades d’entrada i sortida es fa des del programa principal. En començar a programar se us va indicar que la lectura de dades (sigui amb la funció input() per l’entrada estàndard, o de sys.argv si s’entra per argument des de la línia d’ordres) es fes també des del programa principal.
Això no sempre ha de ser així. Es poden definir funcions específiques per gestionar la sortida de les dades. En serien exemples la funció histograma() del mòdul diccionaris i la funció factura() del mòdul supermercat. Són funcions que no retornen res, i la seva única missió és mostrar les dades d’una manera determinada.
Amb la incorporació de l’ús de fitxers en els vostres programes, caldrà revisar els conceptes adquirits fins ara, i creareu funcions per a gestionar els fluxos dels fitxers.
La gestió del flux d’un fitxer (obrir-lo i tancar-lo) es fa habitualment en el programa principal, ja que el control d’aquest flux és important per evitar problemes de memòria o pèrdua de dades (i per gestionar els possibles errors). De la gestió dels errors d’entrada i sortida també se n’encarregarà, en general, el programa principal.
Se sol optar per dividir el procés de lectura o escriptura en funcions especialitzades. Les funcions treballaran i rebran per argument el flux del fitxer, en canvi, els processos d’obertura i tancament aniran a càrrec del programa principal.
La recomanació és fer, sempre que sigui possible:
- El programa principal que gestiona el flux d’inici a final (obertura i tancament del fitxer) i delega la lectura a una funció.
- La funció específica per llegir i carregar les dades, que només necessiti el flux obert (el fitxer) com a argument, sense preocupar-se d’obrir-lo o tancar-lo.
- El mateix plantejament s’utilitzarà per a l’escriptura de fitxers.
- La gestió dels errors d’entrada i sortida la farà el programa principal, amb l’ús dels
try-except.
Això permetrà reutilitzar la funció de lectura amb qualsevol fitxer obert, fent que el codi sigui més versàtil i fàcil de mantenir.
Sempre que sigui possible (ja que en alguns programes cal mantenir oberts al mateix temps els fitxers de lectura i escriptura, i en altres s’han de processar les dades a mesura que es van llegint), cal seguir l’esquema següent:
- Obertura de fitxer de lectura
- Lectura de dades (passant el flux a una funció si s’escau)
- Tancament del fitxer de lectura
- Processament les dades
- Obertura del fitxer d’escriptura
- Escriptura de dades (passant el flux a una funció si s’escau)
- Tancament del fitxer d’escriptura
Exemple de gestió d'usuaris del fitxer ''/etc/passwd''
L’objectiu serà incorporar al mòdul de funcions relacionades amb la gestió dels usuaris del sistema, la funció necessària per a fer un programa que llegeixi els usuaris del sistema.
Aquest és el mòdul en la seva fase inicial:
''' Mòdul de funcions amb utilitats per treballar amb /etc/passwd. L'estructura de dades és la seguent: diccionari_general = {login: {diccionari d'usuari}} diccionari d'usuari = {'UID': '0', 'GUID': '0', 'shell': '/bin/bash'} exemple = {'root': {'UID': '0', 'GUID': '0', 'shell': '/bin/bash'}} ''' # Funcions per crear el diccionari def diccionari_usuari(camps): ''' Funció que crea un diccionari d'usuari a partir dels camps d'una línia del fitxer /etc/passwd Entrada: llista de str Sortida: diccionari ''' dic = {} dic['UID'] = camps[2] dic['GUID'] = camps[3] dic['shell'] = camps[6] return dic def afegir_usuari(linia, dic_total): ''' Funció que afegeix un usuari al diccionari d'usuaris a partir d'una línia del fitxer /etc/passwd ''' camps = linia.strip().split(':') dic_total[camps[0]] = diccionari_usuari(camps) return None # Funcions per fer llistats def usuari_to_str(login, d_usuari): ''' Funció que converteix un diccionari d'usuari a cadena per a escriure el llistat: Entrada: login, diccionari d'usuari Sortida: str ''' return f"login: {login}, UID: {d_usuari['UID']}, GUID: {d_usuari['GUID']}" def llistat(dic_general, taula): ''' Funció per a imprimir la factura, segons l'ordre de la taula Entrada: diccionari d'articles, llista (taula de la compra) Sortida: None ''' for clau, valor in taula: print(usuari_to_str(clau, dic_general[clau])) return None if __name__ == "__main__": # test-drivers if False: print(diccionari_usuari(['root', 'x', '0', '0', 'root', '/root', '/bin/bash'])) if False: dic = {} afegir_usuari('root:x:0:0:root:/root:/bin/bash', dic) afegir_usuari('bin:x:1:1:bin:/bin:/sbin/nologin', dic) print(dic) if True: print(usuari_to_str('root', {'UID': '0', 'GUID': '0', 'shell': '/bin/bash'})) print() if True: dic = {} afegir_usuari('root:x:0:0:root:/root:/bin/bash', dic) afegir_usuari('bin:x:1:1:bin:/bin:/sbin/nologin', dic) taula = sorted(dic.items()) llistat(dic, taula)
Lectura de les dades del fitxer
El primer pas serà gestionar la lectura de les dades del fitxer per a omplir el diccionari d’usuaris. L’objectiu del primer programa serà llegir les dades del fitxer i mostrar el diccionari dels usuaris.
El refinament del programa és el següent:
#obertura del fitxer #lectura de dades #tancament del fitxer #mostrar el diccionari
Només caldrà definir la funció que creï el diccionari a partir del flux del fitxer obert en el programa.
El docstring de la nova funció serà el següent:
def crear_diccionari(flux_fitxer): ''' Funció que llegeix i estructura les dades en un diccionari d'usuaris Entrada: fluxe de fitxer en mode lectura Sortida: diccionari amb les dades dels usuaris. '''
El codi d’aquesta funció serà senzill, ja que es limitarà a recórrer el flux línia a línia i a cridar a la funció afegir_usuari() que ja havíeu definit.
def crear_diccionari(flux_fitxer): ''' Funció que llegeix i estructura les dades en un diccionari d'usuaris Entrada: fluxe de fitxer en mode lectura Sortida: diccionari amb les dades dels usuaris. ''' dic = {} #lectura de dades for linia in flux_fitxer: linia = linia.strip() #per netejar el intro afegir_usuari(linia, dic) return dic
Ja només falta muntar el programa principal, que serà el següent:
import passwd USUARIS = 'etc_passwd.txt' #/etc/passwd per accedir al sistema #obertura del fitxer de lectura fluxe = open(USUARIS, 'r') #lectura de dades dic = passwd.crear_diccionari(fluxe) #tancament del fitxer fluxe.close() #processament de dades print(dic)
Per veure com funciona, podeu utilitzar el fitxer etc_passwd.txt següent:
root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin joan:x:1002:1000:joan,,,:/home/joan:/bin/bash sync:x:4:65534:sync:/bin:/bin/sync games:x:5:50:games:/usr/games:/usr/sbin/nologin
I aquest serà el resultat d’execució:
$ python3 llistats_passwd.py {'root': {'UID': '0', 'GUID': '0', 'shell': '/bin/bash'}, 'daemon': {'UID': '1', 'GUID': '1', 'shell': '/usr/sbin/nologin'}, 'bin': {'UID': '2', 'GUID': '2', 'shell': '/usr/sbin/nologin'}, 'sys': {'UID': '3', 'GUID': '3', 'shell': '/usr/sbin/nologin'}, 'joan': {'UID': '1002', 'GUID': '1000', 'shell': '/bin/bash'}, 'sync': {'UID': '4', 'GUID': '65534', 'shell': '/bin/sync'}, 'games': {'UID': '5', 'GUID': '50', 'shell': '/usr/sbin/nologin'}}
Control d’errors
Per acabar, cal afegir els try-except que gestionin els possibles errors que es poden ocasionar amb l’entrada de les dades.
Com s’ha dit en el capítol del control d’errors, és necessari escriure dues clàusules try niuades, l’exterior per a gestionar els errors ocasionats per la inexistència del fitxer, i la interior per a tots els altres, afegint també la clàusula finally per assegurar-vos del tancament del fitxer.
Seguint la recepta, el programa resultant, amb el control d’errors serà aquest
import passwd USUARIS = 'etc_passwd.txt' #/etc/passwd per accedir al sistema try: #obertura del fitxer de lectura fluxe = open(USUARIS, 'r') try: #lectura de dades dic = passwd.crear_diccionari(fluxe) except IOError as e: print(f"S'ha produït un error inesperat: {e}") exit(1) finally: fluxe.close() except FileNotFoundError: print("Error: No s'ha trobat el fitxer.") exit(1) #processament de dades print(dic)
Que s’ha d’executar amb el mòdul passwd complet:
''' Mòdul de funcions amb utilitats per treballar amb /etc/passwd. L'estructura de dades és la seguent: diccionari_general = {login: {diccionari d'usuari}} diccionari d'usuari = {'UID': '0', 'GUID': '0', 'shell': '/bin/bash'} exemple = {'root': {'UID': '0', 'GUID': '0', 'shell': '/bin/bash'}} ''' # Funcions per crear el diccionari def diccionari_usuari(camps): ''' Funció que crea un diccionari d'usuari a partir dels camps d'una línia del fitxer /etc/passwd Entrada: llista de str Sortida: diccionari ''' dic = {} dic['UID'] = camps[2] dic['GUID'] = camps[3] dic['shell'] = camps[6] return dic def afegir_usuari(linia, dic_total): ''' Funció que afegeix un usuari al diccionari d'usuaris a partir d'una línia del fitxer /etc/passwd ''' camps = linia.strip().split(':') dic_total[camps[0]] = diccionari_usuari(camps) return None # Funcions per fer llistats def usuari_to_str(login, d_usuari): ''' Funció que converteix un diccionari d'usuari a cadena per a escriure el llistat: Entrada: login, diccionari d'usuari Sortida: str ''' return f"login: {login}, UID: {d_usuari['UID']}, GUID: {d_usuari['GUID']}" def llistat(dic_general, taula): ''' Funció per a imprimir la factura, segons l'ordre de la taula Entrada: diccionari d'articles, llista (taula de la compra) Sortida: None ''' for clau, valor in taula: print(usuari_to_str(clau, dic_general[clau])) return None # lectura del fitxer def crear_diccionari(flux_fitxer): ''' Funció que llegeix i estructura les dades en un diccionari d'usuaris Entrada: fluxe de fitxer en mode lectura Sortida: diccionari amb les dades dels usuaris. ''' dic = {} #lectura de dades for linia in flux_fitxer: linia = linia.strip() #per netejar el intro afegir_usuari(linia, dic) return dic if __name__ == "__main__": # test-drivers if False: print(diccionari_usuari(['root', 'x', '0', '0', 'root', '/root', '/bin/bash'])) if False: dic = {} afegir_usuari('root:x:0:0:root:/root:/bin/bash', dic) afegir_usuari('bin:x:1:1:bin:/bin:/sbin/nologin', dic) print(dic) if True: print(usuari_to_str('root', {'UID': '0', 'GUID': '0', 'shell': '/bin/bash'})) print() if True: dic = {} afegir_usuari('root:x:0:0:root:/root:/bin/bash', dic) afegir_usuari('bin:x:1:1:bin:/bin:/sbin/nologin', dic) taula = sorted(dic.items()) llistat(dic, taula)



