Ús avançat de cadenes
En els següents programes, s’exploraran diversos conceptes relacionats amb l’ús avançat de les cadenes, i us centrareu en millorar la robustesa dels programes. A partir d’ara, us assegurareu que l’usuari no pugui introduir dades que puguin fer que el programa no funcioni correctament o produeixin errors (per molt que ho intenti o per molt inepte que sigui).
En cas que les dades no compleixin les especificacions d’entrada, el programa informarà l’usuari i aturarà l’execució. Abans de plantejar aquest control d’errors, revisarem alguns conceptes pendents relacionats amb cadenes.
Treballar amb cadenes
En aquesta secció s’introduiran conceptes com la comparació de cadenes i com l’slicing, i aprendreu a utilitzar alguns mètodes predefinits del Python que treballen amb dades de tipus str. També aprendre a formatar-les perquè s’adaptin a les vostres necessitats.
Primer programa amb els nous conceptes sobre cadenes
Els conceptes nous que aprendreu es poden resumir en la nova versió del programa que llegeix una cadena i comprova si es pot convertir en un nombre de punt flotant (o que no produirà errors si se li aplica la funció float() de Python).
Ara escriviu un programa que llegeix una cadena; si és un float escriu el seu valor amb dos decimals. En cas contrari indica que no es pot convertir a punt flotant. El programa ha d’estar preparat per a rebre la cadena buida.
Les especificacions d’entrada són una cadena qualsevol (inclosa la cadena buida), i el joc de proves que es mostra en la taula.
El codi del programa és el següent:
#!/usr/bin/python3
#-*- coding: utf-8-*-
#
# Escola del Treball de Barcelona
# Administració de Sistemes informàtics
# Curs 2019-20
#
# Versió: 2
#
# Descripció: Fes un programa que digui si una cadena és un float o no.
# Ha de ser True en qualsevol cas en que no peti la funció float() de Python
# i False en cas contrari
# Versió 2 (utilitzant split())
#
# Especificacions d'entrada: una cadena qualsevol
# ATENCIÓ: ës molt important que funcioni pet TOTS els casos (el joc de proves
# l'ha fet el vostre pitjor enemic)
# Joc de proves
# Entrada: Sortida:
# -3 T
# +3.5 T
# 3. T
# .3 T
# . F
# 2.2.2.3 F
# 3,4 F
# 34.2a F
# +-3 F
# +
# '' (cadena buida)
entrada = input()
cadena = entrada #per mantenir el valor inicial
#treure el signe si cal
if cadena[0:1] in '+-':
cadena = cadena[1:]
# trossejar pel primer punt
punt = cadena.find('.')
cadena2 = ''
if not (punt == -1):
cadena = cadena[:punt]
cadena2 = cadena[punt + 1:]
# fa el mateix que el mètode isdigit()
cadena_es_digit = True
for c in cadena:
if c < '0' or c >'9':
cadena_es_digit = False
# elimar casos especials
es_float = (cadena_es_digit or cadena == '') and (cadena2.isdigit()
or cadena2 == '') and cadena != '.' and cadena != ''
# mostrar un resultat formatat
if es_float:
print(f'{entrada} té un valor {float(entrada):.2f}')
else:
print(f'{entrada} no es pot convertit a float')
| Entrada | Sortida |
|---|---|
| -3 | -3 té un valor -3.00 |
| +3.5 | +3.5 té un valor 3.50 |
| 3. | 3. té un valor 3.00 |
| -00.3 | -00.3 té un valor -0.30 |
| . | . no es pot convertit a float |
| 2.2.2.3 | 2.2.2 3 no es pot convertit a float |
| 3,4 | 3,4 no es pot convertit a float |
| 34.2a | 34.2a no es pot convertit a float |
| +-3 | +-3 no es pot convertit a float |
| 23-3.4 | 23-3.4 no es pot convertit a float |
| + | + no es pot convertit a float |
| (cadenabuida) | no es pot convertit a float |
I el podeu veure amb números de línia a la figura.
Els nous conceptes que apareixen a aquest programa són els següents:
- Línia 40: s’utilitza la funció de subcadenes o slicing per evitar errors si la dada llegida és la cadena buida (i no té posició 0).
- Línia 41: subcadena evitant el signe.
- Línia 44: s’utilitza el mètode
find()destr. - Línia 46, 47 i 48: si existeix un punt creem la subcadena anterior i posterior.
- Línies de la 51 a la 54: fragment de programa per comprovar si una cadena està formada únicament de dígits. Fa el mateix que el mètode
isdigit()que s’utilitza a la línia 56 ambcadena2. - Línia 53: operadors de comparació de cadenes.
- Línies 61 i 63: ús de les
f’stringsper a formatar cadenes.
Comparació de cadenes
Ja sabeu com funcionen els operadors d’igualtat (== ) i desigualtat ( !=) entre cadenes. Són operadors binaris, es poden utilitzar entre qualsevol mena de dada (i entre una cadena i una altra qualsevol). El resultat és una dada de tipus booleà. Dues cadenes són iguals només si es tracta de la mateixa cadena.
Existeixen altres operadors relacionals que permeten comparar cadenes. Són els símbols >, <, >= i ⇐. Per poder comparar dos tipus de dades cal que estigui definida una relació d’ordre entre elles. L’ordre entre les cadenes és l’ordre lexicogràfic.
Entre les dades de tipus str, es defineix una relació d’ordre coneguda com a ordre lexicogràfic. Aquesta relació es basa en la representació dels caràcters en la Taula ASCII o en el codi Unicode.
A cada caràcter se li assigna un nombre que és la seva posició en la taula. A partir d’aquí es defineix una relació on es pot dir sense ambigüitats si un caràcter és major, menor o igual que un altre (i només són iguals si és exactament el mateix).
El procés de comparació entre dues cadenes comença per la comparació de la posició dels seus primers caràcters. Si aquests són iguals, la comparació continua amb els caràcters següents, i així successivament. Aquest mètode estableix una seqüència d’ordre que segueix la disposició alfabètica dels caràcters en la codificació utilitzada.
Els operadors de comparació >, <, >=, ⇐ dels str són operadors binaris que només poden operar sobre dues dades de tipus str. El resultat sempre serà un valor booleà.
Comparació de cadenes
Vegeu els exemples de relació d’ordre lexicogràfic:
L’ordre lexicogràfic se sol relacionar amb la posició al diccionari. Però, a diferència del diccionari, la posició de majúscules i minúscules no és la mateixa, ja que totes les majúscules estan situades abans que les minúscules.
>>> 'a' < 'b' True >>> 'a' < 'bcde' True >>> 'a' < 'ab' True >>> 'python' <= 'pepe' False >>> 'python' < 'Python' False >>> 'Python' >= '0' True
Fixeu-vos en el resultat d’aquesta comparació:
>>> 'a' < 'ab' True
La cadena buida no es considera un caràcter, de manera que no es pot buscar la seva posició (o valor) en el codi ASCII:
>>> ord('')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: ord() expected a character, but string of length 0 found
Però la cadena buida és considerada menor que qualsevol cadena no buida, ja que no té cap caràcter per comparar. Això és consistent amb la convenció d’ordre lexicogràfic i facilita la seva utilització en comparacions de cadenes a Python.
Com podeu deduir dels exemples, les minúscules van després de les majúscules en la taula ASCII, i les xifres estan situades abans que les majúscules.
Recordeu que el caràcter ‘1’ té la posició següent al ‘0’, la lletra ‘B’ la posterior a la ‘A’ i la ‘b’ va immediatament després de la ‘a’.
>>> ord('0')
48
>>> ord('A')
65
>>> ord('a')
97
Expressió booleana per definir un caràcter que representa una xifra
Considereu la següent condició:
c >= '0' and c <= '9'
Utilitzeu l’intèrpret interactiu per comprovar que si c és un caràcter, els valors que compleixen aquesta condició són únicament les xifres.
Aquesta expressió booleana, en el cas que c sigui un caràcter, és idèntica a:
ord(c) >= ord('0') and ord(c) <= ord('9')
que avalua el caràcter contingut a la variable c. Segons la seva posició a la taula ASCII, que obtenim amb ord(), podrem valorar si es troba entre els caràcters numèrics 0 i 9.
Comparacions entre str de longitud superior a 1
Atenció, però, ja que si cad és un str format per més d’un caràcter, el resultat deixa de ser tan intuïtiu:
>>> c = '3' >>> c >= '0' and c <= '9' True >>> c = 'a' >>> c >= '0' and c <= '9' False >>> cad = '92' >>> cad >= '0' and cad <= '9' False >>> cad = '1abc' >>> cad >= '0' and cad <= '9' True
Mentre el caràcter sigui igual entre les dues cadenes s’ha de comprovar el següent parell de caràcters. En el moment en que es troba un parell de caràcters en la mateixa posició amb un valor diferent, la cadena que tingui el caràcter amb el codi ASCII més petit serà la ‘menor’.
Comparacions de cadenes numèriques
Quan es comparen cadenes formades per nombres (o que es poden convertir en nombres) cal anar amb compte, ja que de vegades no coincideixen amb el que s’obté en fer la mateixa comparació entre nombres.
Fixeu-vos en els exemples següents:
>>> '1' < '2' True >>> '11' < '2' True >>> '19999' < '2' True
Slicing o fer subcadenes
L’operador de slicing permet obtenir trossos d’una cadena. Per fer-la servir cal indicar les posicions del primer element del fragment que volem obtenir, i de l’element inmediatament posterior al darrer que volem obtenir. Per exemple:
>>> a = 'Paraules blanques' >>> a[1:4] 'ara' >>> a[9:17] 'blanques'
L’operador de slicing permet fer trossos de la cadena, o subcadenes. Funciona de la manera següent:
cadena[inici:final]On inici és la posició en la qual es comença a “retallar” i s’acaba abans de la posició final.
Els valors d’inici i final sempre han de ser nombres enters. A diferència de la indexació, l’operador deslicing mai produeix un error: considera que sempre pot retornar la cadena buida si els índexs són inexistents.
Si no es posa cap valor d’inici, es farà la cadena des del primer caràcter fins a la posició indicada per final:
>>> a = 'Paraules blanques' >>> a[:8] 'Paraules'
Si no es posa cap valor de final, es construirà la cadena des del caràcter indicat per inici fins a l’últim:
>>> a = 'Paraules blanques' >>> a[9:] 'blanques'
A continuació teniu més exemples d’ús de l’operador de subcadenes:
>>> a = 'el Python és molt fàcil' >>> a[3:9] 'Python' >>> a[3:] 'Python és molt fàcil' >>> a[:9] 'el Python' >>> a[:] 'el Python és molt fàcil'
Comparació entre la indexació i l'slicing
A continuació es mostren els resultats que donen l’ús d’un operador o un altre sobre una cadena. El més important és que amb l’slicing també es pot indexar (un caràcter no deixa de ser una subcadena). I que l’slicing no produeix errors sinó que retorna la cadena buida en cas de no trobar cap subcadena que compleixi les condicions.
>>> a = 'el Python és molt fàcil' >>> a[30:40] '' >>> a[30] Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: string index out of range >>> a[:-1] 'el Python és molt fàci' >>> a[-1] 'l' >>> a[:len(a)] 'el Python és molt fàcil' >>> a[len(a)] Traceback (most recent call last): File "<stdin>", line 1, in <module> IndexError: string index out of range >>> a[:30] 'el Python és molt fàcil' >>> a[len(a): len(a) + 1] ''
Mètodes de cadenes
Les cadenes són objectes que pertanyen al tipus de dades string. Aquests objectes tenen associats diversos mètodes, que són funcions específiques que poden ser cridades directament sobre una cadena.
A diferència de les funcions, que són cridades amb la sintaxi funcio(argument1, argument2…), els mètodes es criden sobre un objecte específic amb la sintaxi objecte.metode(argument1, argument2…).
Com cridar un mètode d'una cadena
Si teniu una cadena anomenada cadena1, s’invoquen els mètodes afegint un punt i el nom del mètode. Els mètodes són com funcions (i, per tant, sempre aniran amb parèntesis) que poden tenir cap o diversos arguments. La diferència és que sempre actuen sobre l’objecte que els crida.
>>> cadena1 = 'hola'
>>> cadena1.isdigit()
False
>>> cadena1.find('h')
0
>>> cadena1.find('0')
-1
En aquest exemple s’ha utilitzat el mètode isdigit() sobre la cadena “hola” . Aquest mètode retorna True o False en funció de si la cadena està formada únicament per xifres (números) o no. És un mètode que no necessita argument, però actua sobre la cadena que el crida: si el crida la cadena “hola”, retornarà False. Però si és cridat per “234” retornarà True.
El mètode find() necessita un argument, que és el caràcter (o subcadena) que ha de buscar. Si el caràcter es troba en la cadena que el crida, retornarà la seva primera posició. Si no hi és, retornarà -1. Els mètodes de cadenes només poden ser cridats per una cadena. Si un tipus de dada intenta accedir a un mètode que no té definit es produirà un error.
Intent de crida d'un mètode de str per un int
>>> a = 231
>>> a.find('1')
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'int' object has no attribute 'find'
Finalment, explorarem alguns dels mètodes de cadenes. Les cadenes són un tipus de dada incorporat a Python i ja tenen molts mètodes per operar sobre elles. Per consultar-los, podeu donar una ullada a la documentació amb la funció help(), amb el nom de la classe passat com a argument (help() és una funció i no és invocada des de cap objecte, a diferència dels mètodes).
help(str)
Apareixerà una pàgina amb el més rellevant de la classe.
Help on class str in module builtins: class str(object) | str(object='') -> str | str(bytes_or_buffer[, encoding[, errors]]) -> str | | Create a new string object from the given object. If encoding or | errors is specified, then the object must expose a data buffer | that will be decoded using the given encoding and error handler. | Otherwise, returns the result of object.__str__() (if defined) | or repr(object). | encoding defaults to sys.getdefaultencoding(). | errors defaults to 'strict'. | | Methods defined here: | | __add__(self, value, /) | Return self+value. | | __contains__(self, key, /) | Return key in self. | :
Els primers mètodes són mètodes especials dunder (abreviació de double underscore). Aquests mètodes tenen un significat especial i són cridats automàticament en certes circumstàncies.
Després venen els mètodes que ens interessen ara. És aconsellable que jugueu amb ells a l’intèrpret interactiu per a familiaritzar-vos una mica amb ells, però sempre podeu recórrer a l’ajuda del help().
Alguns mètodes de str
count(substring). Aquest mètode retorna el nombre de vegades que una subcadena apareix a la cadena.>>> text = "hello world" >>> text.count('h') 1find(substring). Aquest mètode retorna la primera posició de la subcadena a la cadena. Si no es troba, retorna -1.>>> text = "hello world" >>> text.find('o') 4index(substring). Similar afind(), aquest mètode retorna la primera posició de la subcadena a la cadena. Si no es troba, genera una excepcióValueError.>>> text = "hello world" >>> text.index('o') 4 >>> text.index('a') Traceback (most recent call last): File "<stdin>", line 1, in <module> ValueError: substring not foundisdigit(). Aquest mètode retornaTruesi tots els caràcters de la cadena són dígits (0-9), si no retornaFalse.>>> num = '123' >>> num.isdigit() True
islower(). Aquest mètode retornaTruesi tots els caràcters de la cadena són minúscules, si no retornaFalse.>>> text = 'hello' >>> text.islower() True >>> text = 'Hello' >>> text.islower() False
isupper()com islower però amb les lletres majúscules.lower(). Aquest mètode retorna una nova cadena amb tots els caràcters convertits a minúscules.>>> text = 'Hello123' >>> text.lower() 'hello123'
- replace(old, new). Aquest mètode substitueix totes les instàncies d’una subcadena amb una altra.
>>> text = 'Hello World' >>> text.replace('Hello', 'Hola') 'Hola World' upper(). Aquest mètode retorna una nova cadena amb tots els caràcters convertits a majúscules.
:::
Format de cadenes
El format de cadenes fa referència a la manera en què podeu organitzar i estructurar el contingut d’una cadena de text a dins del vostre codi. En Python, hi ha diverses maneres de formatar cadenes, i a continuació teniu una breu explicació d’algunes d’elles. En tots els exemples suposarem que s’han definit les variables.
>>> nom = 'Pep' >>> edat = 30
Diferents maneres de formatar una cadena
Algunes de les eines per donar format a una cadena són les següents:
- Operador
%. Amb aquest operador, es poden utilitzar marcadors de posició (o placeholders) a la cadena i després proporcionar els valors que es volen substituir utilitzant l’operador %.>>> 'Em dic %s i tinc %d anys' % (nom, edat) 'Em dic Pep i tinc 30 anys'
Cal descriure el tipus de dada que s’insereix en la cadena. En aquest exemple, %s indica que és un
stri %d unint(o decimal). - Mètode
str.format(): Aquest mètode utilitza placeholders {} a la cadena i proporciona els valors mitjançant el mètode de cadenes format().>>> "Em dic {} i tinc {} anys.".format(nom, edat) 'Em dic Pep i tinc 30 anys.' F-strings(of-strings): Introduïdes a partir de Python 3.6, lesf-stringspermeten incrustar expressions de Python directament dins de cadenes.>>> f'Em dic {nom} i tinc {30} anys.' 'Em dic Pep i tinc 30 anys.'Les
f-stringssón àmpliament adoptades i considerades una de les maneres més eficaces i llegibles de formatar cadenes. És el mètode que s’utilitzarà en aquest curs, ja que és el més fàcil i simple.
Usos més comuns de les f-strings
Aquí teniu alguns dels seus usos més comuns:
- Realitzar operacions.
>>> f'resultat = {3 / 7}' 'resultat = 0.42857142857142855' - Determinar la quantitat de decimals que es mostren en un float.
>>> f'resultat = {3 / 7:.2f}' 'resultat = 0.43' - Omplir amb espais en blanc o zeros.
>>> h = 9 >>> m = 10 >>> f'hora:{h}:{m}' 'hora:9:10' >>> f'hora:{h:5}, minut{m:5}' 'hora: 9, minut 10' >>> f'hora:{h:25}:{m:25}' 'hora: 9: 10' >>> f'{h:02}:{m:02}' '09:10' >>> f'hora:{h:04}:{m:04}' '0009:0010'
Existeixen moltes més possibilitats, però aquestes són les que més utilitzareu.
Conceptes clau
Els elements fonamentals que heu de conéixer bé per a poder treballar àgilment amb cadenes són la comparació, el slicing, els métodes disponibles per cadenes, i les f-strings. A continuació teniu els aspectes clau per fer-los servir.
Comparació de cadenes
Les cadenes es comparen de manera lexicogràfica. Una cadena és menor que una altra si el codi ASCII del primer element diferent és menor, o si tots els elements són iguals i és de longitud menor que l’altra.
Un exemple de comparació de cadenes que evidencia això és el següent:
>>> 'Python' < 'Python3' True
Slicing
L’operador slicing [:] permet retallar i crear subcadenes. Cada subcadena és una nova instància de la cadena original.
Al següent exemple podeu veure l’ús de slicing:
>>> versio = 'Python3.6' >>> start = 6 >>> stop = 9 >>> versio[start:stop] '3.6'
El codi crea la subcadena que comença en start i acaba una posició anterior a stop.
L’operador de slicing torna la cadena buida si no existeixen caràcters entre les posicions demanades.
Mètodes de cadenes
Per veure tots els mètodes definits sobre cadenes podeu utilitzar la funció help().
help(str)
Un mètode és essencialment una funció, però que actua sobre l’objecte que la crida. El resultat seria el mateix que passar-li a una funció aquest objecte com a argument. Un mètode s’invoca amb la nomenclatura objecte.metode(arguments), on els arguments poden ser-hi o no.
Al següent exemple podeu veure la crida d’un mètode:
>>> 'Hola'.upper() 'HOLA'
Es crida i actua sobre la cadena ‘hola’, passant les lletres a majúscules.
La diferència entre un mètode i una funció és que al mètode upper() no li cal argument, ja que actua sobre l’objecte que el crida. Si fos una funció, caldria passar-li la cadena ‘hola’ com a argument i es cridaria:
upper('hola')
F-strings
A partir del Python 3.6 es poden utilitzar les f-strings per a formatar cadenes. S’escriu la cadena tal com es vol, precedida de la lletra f (o F) i posant el nom de la variable que voleu utilitzar entre els parèntesis '{}'.
Al següent exemple de f’string es dona un format de sortida a la cadena “Python” afeging-li el valor de versio:
>>> versio = '3.6'
>>> f'Python{versio}'
'Python3.6'
Les f’strings permeten moltes opcions per formatar la cadena, les que més utilitzareu són:
- Determinar el nombre de xifres després de la coma d’un nombre float.
>>> f'resultat = {3 / 7:.2f}' 'resultat = 0.43' - Determinar els zeros o espais en blanc en escriure un nombre enter.
>>> f'{3:02d}:{0:02d}' '03:00'
Cadenes i control d’errors
Fins ara, els nostres programes no han estat prou robustos. Hem limitat les nostres pràctiques a simplement especificar les condicions d’entrada, sense tenir en compte les conseqüències si un usuari introdueix dades per a les quals el programa no està preparat. En aquesta secció, explorareu com controlar les entrades per prevenir errors quan les dades proporcionades no són adequades per al programa.
Un programa és robust quan no es pot produir un error de cap manera, independentment de les dades que es donin com a entrada.
En els programes que esteu escrivint fins ara les dades entres per stdin. Més endavant aprendreu a fer-les entrar per la línia d’ordres. Però tant en un cas com en l’altre, les dades que apareixen en el programa sempre ho fan en tipus str. Per això el control d’errors va lligat al tipus de dada str, tot i que els conceptes són generals i es poden aplicar a dades llegides de diferents maneres.
Hi ha diverses filosofies per abordar el tractament dels errors, però per a un administrador de sistemes és clar: si el programa no està preparat per executar-se per una entrada concreta, ha de mostrar un missatge d’ús i finalitzar l’execució, similar al que fan les ordres de Linux. No s’han de donar oportunitats per a introduir la dada correcta ni fer suposicions sobre les intencions de l’usuari. Abans de començar l’execució, realitzareu una comprovació per assegurar-vos que les dades coincideixen amb les especificacions d’entrada. Si no és així, es mostrarà un missatge i es finalitzarà l’execució, sortint directament del programa.
Primer programa amb control d'errors
Els conceptes nous que aprendreu es poden resumir en la nova versió del programa del càlcul de la lletra del DNI, afegint un control d’errors per a fer-lo robust.
Recordeu l’enunciat del programa: El número del DNI és una combinació única de xifres que serveix per identificar-nos com a ciutadans. La lletra del DNI té una funció de control. L’assignació de la lletra no és aleatòria, és el resultat d’una operació matemàtica. Per saber quina lletra us correspon heu de dividir el número del DNI entre 23. El residu de l’operació és el que es fa servir per assignar la lletra. Escriviu un programa que llegeixi un DNI i calculi la lletra de control. Les especificacions d’entrada són: la part numèrica d’un DNI correcte.
El codi per al programa lletra_dni.py pot ser aquest:
LLETRES_DNI = 'TRWAGMYFPDXBNJZSQVHLCKE' #llegir nombre dni = int(input()) #càlcul del mòdul pos = dni % len(LLETRES_DNI) #mostrar la lletra print(LLETRES_DNI[pos])
El que indica que la part numèrica d’un DNI és correcta és:
- Que tots els caràcters són numèrics.
- Que té exactament 8 caràcters.
- Que no comença per 0.
El control d’errors consisteix a eliminar totes les entrades que no coincideixin amb les especificacions d’entrada. A codi següent hi ha una nova versió, amb el control d’errors incorporat, del programa de buscar la lletra del DNI.
# Descripció: Calcular la lletra del DNI # E E: un número de dni # Versió 4 Amb control d'errors BEN FET MISSATGE = 'DNI correcte: 8 dígits sense començar per 0' LLETRES_DNI = 'TRWAGMYFPDXBNJZSQVHLCKE' # CONTROL D'ERRORS dni_c = input() # comprovar que sigui enter positiu if not dni_c.isdigit(): print(MISSATGE) exit(1) # comprovar que la longitud sigui 8 if not len(dni_c) == 8: print(MISSATGE) exit(1) # comprovar que no comenci per 0 if dni_c[0] == '0': print(MISSATGE) exit(1) dni = int(dni_c) # ara es pot convertir a enter # PROGRAMA PRINCIPAL pos = dni % len(LLETRES_DNI) print(LLETRES_DNI[pos])
A la figura teniu el mateix codi amb els números de línia:
Els nous conceptes sobre control d’errors que apareixen en el codi anterior són els següents:
- En la línia 13 hi ha una constant de tipus
strque conté el missatge que es donarà quan hi ha un error. Convé seguir les convencions de Linux i no cal especificar amb un missatge diferent la causa de cada error. - En les línies 20, 25 i 30 es testeja una condició. L’expressió booleana determina alguna característica de la dada que no acompleix les especificacions d’entrada.
- En les línies 21, 26 i 31 es dona un missatge d’error per informar a l’usuari que l’entrada no és correcta.
- En les línies 22, 27 i 32 s’interromp l’execució amb la funció
exit().
exit() és una partícula que està “vetada” per la programació estructurada. Però es considera adequat l’ús d’aquest tipus de funcions en el control dels errors perquè augmenten la llegibilitat dels programes. Si s’ha arribat a la línia 36 és que l’entrada és correcta i es procedeix a executar el programa.
Interrupció causada pel processador
Si no es respecten les condicions d’entrada, poden ocórrer diverses coses, des que el programa realitzi operacions sense sentit que el processador generi un error, interrompent l’execució perquè no és capaç de realitzar alguna de les instruccions.
Interrupció no controlada
Si en el programa lletra_din.py introduïu 1234567P, el processador es queixa:
videoioc@debian-xtec:~/Documents/programacio$ python3 dni.py
1234567P
Traceback (most recent call last):
File "/home/videoioc/Documents/programacio/dni.py", line 3, in <module>
dni = int(input())
ValueError: invalid literal for int() with base 10: '1234567P'
Es llença un error en temps d’execució, en aquest cas un ValueError. Aquest error fa que el programa no pugui continuar i s’interromp de manera brusca.
Els programadors no poden permetre que els seus programes acabin d’aquesta manera i han d’evitar que es produeixin aquests errors. La filosofia de molts programes interactius és continuar executant-se fins a aconseguir que l’usuari escrigui una dada vàlida. Però el que fareu com a administradors és finalitzar el vostre programa, de manera neta, abans que es produeixi l’error. Es mostrarà un missatge explicant el funcionament i si l’usuari ho vol tornar a intentar, caldrà que l’executi de nou.
Evitar que es produeixi l'error amb if/else
El primer que es pensa per a evitar que el programa emeti un error si l’entrada no és bona, és fer una estructura if/else, en una branca de la qual es faci l’operació quan la dada és correcta, i en l’altra es finalitzi l’execució de manera controlada si la dada introduïda no és correcta.
Control d'errors amb if/else.
A continuació es mostra el codi del programa lletra_dni.py si es fa el control d’errors amb if/else. Hi ha una branca en la qual es descarten les entrades no numèriques perquè el processador pugui aplicar la funció int(). Si també es descarten les altres dades que no acompleixen les especificacions d’entrada, el programa quedaria així:
MISSATGE = 'DNI correcte: 8 dígits sense començar per 0' LLETRES_DNI = 'TRWAGMYFPDXBNJZSQVHLCKE' # CONTROL D'ERRORS dni_c = input() # comprovar que sigui enter positiu if not dni_c.isdigit(): print(MISSATGE) else: # comprovar que la longitud sigui 8 if not len(dni_c) == 8: print(MISSATGE) else: # comprovar que no comenci per 0 if dni_c[0] == '0': print(MISSATGE) else: #PROGRAMA PRINCIPAL dni = int(dni_c) pos = dni % len(LLETRES_DNI) print(LLETRES_DNI[pos])
Utilitzar les branques del if/else per a fer el control d’errors genera dos problemes:
- El primer és la llegibilitat: és difícil distingir què és control d’errors i què és el programa. S’han de llegir moltes línies fins a descobrir on està i què fa el programa principal.
- El segon problema és la reusabilitat. Si no necessiteu fer control d’errors (per exemple, si llegiu els DNI d’un fitxer que teniu la seguretat que és correcte), és difícil de “retallar” el programa principal.
En definitiva, no és una bona tècnica controlar els errors amb l’estructura if/else.
Evitar que es produeixi l'error amb exit()
La instrucció exit() és utilitzada per finalitzar immediatament l’execució d’un programa.
Quan la instrucció exit() s’executa, el programa s’atura sense continuar amb cap altra línia de codi, independentment d’on es trobi el punt d’execució. És una funció que admet un argument enter, que s’anomena codi de sortida del programa.
La instrucció exit() és molt útil en el control d’errors, però no es pot utilitzar en el programa principal (perquè no seria programació estructurada). Sempre, abans de cridar aquesta instrucció, heu de proporcionar un missatge explicatiu sobre com utilitzar el programa.
Incorporant aquesta funció no necessitareu la branca else, ja que si s’entra en l’ if i hi ha l‘exit() finalitza el programa. Només es passa al bloc del segon if si s’ha pogut superar el primer, és a dir, en el cas que compleixi la primera condició de les especificacions d’entrada. Si s’arriba al programa principal, significa que l’entrada compleix les especificacions estipulades per a ser apta com a entrada del programa.
Descartar dades no numèriques
En el següent fragment de codi es descarten les dades que no estan formades únicament per dígits, interrompent el programa quan es produeix aquest cas.
# comprovar que sigui enter positiu if not dni_c.isdigit(): print(MISSATGE) exit(1)
La condició que s’ha de posar en l’ if que conté l’ exit() és la condició dolenta, la que no compleix les condicions d’entrada. Si, com en l’exemple, el que veieu clar és l’expressió que sí que permet l’entrada de les dades, l’haureu d’escriure amb un not davant.
Fixeu-vos en la funció exit(), que ha estat cridada amb l’argument de valor 1.
Els codis de sortida són una forma estàndard de comunicar l’estat final d’un programa i són particularment útils en scripts o en la línia d’ordres, on altres scripts o programes poden prendre decisions basades en aquesta informació.
Un codi de sortida és un valor numèric que un programa retorna al sistema operatiu o a l’entorn d’execució quan finalitza la seva execució. Aquest valor indica l’estat final del programa i pot ser interpretat pel sistema operatiu o altres programes que el cridin com a indicatiu del seu èxit o fracàs.
Els valors dels codis de sortida poden variar d’un sistema operatiu a un altre, però hi ha algunes convencions comunes:
- Codi de sortida 0: generalment indica que el programa ha finalitzat amb èxit i sense problemes importants. És el valor “èxit” per convenció.
- Altres codis de sortida (diferents de 0): normalment indiquen que hi ha hagut algun tipus d’error o anomalia en la finalització del programa. El valor exacte i el seu significat poden dependre del context del programa i del sistema operatiu.
Com a administradors de sistemes haureu de documentar els vostres scripts i programes amb la interpretació de cada codi de sortida.
En aquest curs de programació ens limitarem a utilitzar els valors 0 (el programa l’envia per defecte si finalitza la seva execució), o el valor 1 que és el que es proporcionarà quan s’ha interromput l’execució amb exit(), és a dir, quan no s’han seguit les especificacions d’entrada en les dades introduïdes al programa.
Avantatges de fer el control d'errors amb exit()
Posar el control d’errors abans del programa principal i utilitzar exit() per finalitzar l’execució quan les dades no són adequades ofereix diversos avantatges:
- Llegibilitat: mantenir el programa principal a la primera columna facilita la identificació ràpida de la lògica principal del programa. Aquesta estructura clara ajuda els programadors (incloent-vos a vosaltres mateixos o altres persones que revisin el codi) a comprendre ràpidament com funciona el programa sense haver de seguir indentacions complicades.
- Evita confusions: en llenguatges com Python, la indentació s’utilitza per definir els blocs de codi. Si el programa principal no comença a la primera columna, podria conduir a confusions sobre quin és el punt d’entrada principal del programa.
- Facilita el seu manteniment: tenir el control d’errors separat del cos principal del programa fa que sigui més senzill mantenir i modificar el codi. És més fàcil trobar i modificar les comprovacions d’errors sense afectar la lògica principal del programa.
- Flexibilitat en la validació d’entrades: quan la gestió d’errors és modular i es pot adaptar fàcilment, és més fàcil reutilitzar els components de validació d’entrades en altres projectes. Aquesta flexibilitat és important per adaptar-se als requisits específics de diferents contextos sense haver de reescriure tot el codi.
- Tests de validació independents: si els tests de validació són independents del programa principal, és més fàcil provar-los de manera aïllada i assegurar-se que funcionin correctament en diverses situacions. Això augmenta la confiança en la reutilització d’aquests tests.
Tractar l'error recollint la excepció
Una excepció, anomenada Exception en Python, és una classe de Python que s’utilitza per a gestionar errors produïts durant l’execució d’un programa. Permet recollir-los amb les clàusules try, except i finally (opcional) i tractar-los adequadament.
Quan es produeix una excepció, el flux normal del programa es desvia i s’executa un bloc de codi especial anomenat gestor d’excepcions. Aquest és el motiu pel qual les excepcions no segueixin les convencions de la programació estructurada.
Així i tot proporcionen un mecanisme molt potent per gestionar errors i situacions excepcionals, contribuint a la robustesa del codi i facilitant la identificació i resolució d’errors. N’hi ha prou amb deixar que es produeixi l’error, recollir-lo i tractar-lo (que en el cas del programa d’un administrador de sistemes sol ser interrompent-lo, mostrar un missatge i invocar exit()).
La teoria sobre quan generar i com tractar les exceptions, però, va més enllà dels objectius d’aquest curs, de manera que no les podreu utilitzar ni com a mecanisme per al control d’errors.
Exemple de control d'errors amb la classe Exception
Aquí es presenta un exemple simple amb l’ús de les excepcions. El programa, però, només controla que l’entrada sigui un enter, sense tenir en compte que el nombre llegit tingui 8 caràcters sense començar per 0.
MISSATGE = 'DNI correcte: 8 dígits sense començar per 0'
LLETRES_DNI = 'TRWAGMYFPDXBNJZSQVHLCKE'
#lectura de dades
entrada = input()
#CONTROL D'ERRORS
try:
dni = int(entrada)
# Aquesta línia no s'executarà si hi ha una excepció
print("Aquesta línia s'executarà només si la conversió és exitosa.")
except:
# Gestionar l'excepció
print(MISSATGE)
exit(1)
# PROGRAMA PRINCIPAL
pos = dni % len(LLETRES_DNI)
print(LLETRES_DNI[pos])
Les excepcions són un mecanisme molt potent, però cal aprendre a utilitzar-les bé. En aquest curs no estaran permeses ni pel control d’errors.
El joc de proves del control d’errors
La gestió d’errors implica crear un programa que interromp l’execució si les dades no acompleixen les especificacions d’entrada, assegurant-vos així que el programa es comporta de manera controlada en totes les situacions.
El joc de proves per al control d’errors és el terme que es dona a un llistat de possibles entrades juntament amb el valor True o False, que indica que la dada és acceptada i es processarà, o si el programa ha de tallar l’execució perquè pot provocar errors, perquè no interessa o perquè no té sentit.
El joc de proves dels errors s’ha de considerar com un programa a part, en el que es determina si les dades d’entrada són aptes o no per accedir al programa.
És crucial dissenyar un joc de proves del control d’errors abans de codificar-lo. Aquesta bateria de proves és independent de la que s’ha de fer abans de pensar l’algorisme de resolució del programa principal.
Joc de proves dels errors del programa lletra_dni.py
Les especificacions d’entrada són: Una cadena que compleix les condicions:
- Que tots els caràcters són numèrics.
- Que té exactament 8 caràcters.
- Que no comença per 0.
Cal considerar com a mínim un cas de dada que no acompleixi alguna de les especificacions. En la taula hi ha el joc de proves del control d’errors. Fixeu-vos que les sortides del joc de proves només poden ser True o False
| Entrada | S’accepta? |
|---|---|
| 12345678 | True |
| 123456789 | False |
| no tinc dni | False |
| 23.378.887 | False |
| -1234567 | False |
| 01234567 | False |
| 1234567F | False |
Ara cal considerar el control d’errors com a un programa independent, amb una característica que el diferencia dels altres programes: No només podeu utilitzar la instrucció exit(), sinó que és necessari que ho feu.
El programa resultant, ja incorporat al programa principal del càlcul de la lletra del DNI, és el que teniu a la figura
Conceptes clau
Per a fer els programes robustos, cal desfer-se de les dades que poden causar efectes indesitjats abans d’executar el programa principal. La manera en què tractarem els errors en aquest curs serà utilitzant una estructura condicional i la instrucció exit().
El control d’errors s’ha de pensar una vegada el programa principal ja està fet i verificat. Es pot considerar com un programa en si mateix (amb la particularitat que no seguirà les convencions de la programació estructurada). S’afegeix abans del programa principal i el seu objectiu és detectar les dades que no estan a les especificacions d’entrada i acabar l’execució sense que entrin al programa principal. Es donarà un missatge d’error i es cridarà a exit().
El valor de sortida que aquesta funció proporcionarà al sistema operatiu quan les dades no són correctes serà 1. Si s’aconsegueix executar el programa, per defecte es retorna el valor 0. El valor de sortida és l’argument de la funció. L’estructura del control d’errors sempre serà la següent:
# lectura de dades # CONTROL D’ERRORS if (dada que no va bé): print(missatge) exit(1) … # PROGRAMA PRINCIPAL
Hi pot haver tants if com sigui necessari. En el moment que la dada entra al programa principal heu d’estar segurs que acompleix les especificacions d’entrada.





