Conceptes avançats de la programació modular
Fins ara heu après a definir les funcions segons el refinament del programa. Ara passareu a aprendre sobre l’àmbit de les variables (concepte clau per entendre com han d’entrar les dades en una funció per seguir els consells de la programació estructurada), i el Principi de responsabilitat única (SRP) que s’ha de tenir sempre en compte en el moment de dissenyar les funcions que es cridaran en un programa.
Àmbit de les variables
En la majoria dels llenguatges de programació, incloent-hi Python, les variables tenen un àmbit associat que és determinat pel bloc de codi en què han estat declarades.
L’àmbit d’una variable fa referència a la part del codi on aquesta variable és vàlida i accessible. En altres paraules, és la regió del programa on la variable pot ser utilitzada i referenciada.
Variables locals i variables globals
Tenint en compte l’àmbit de visibilitat d’una variable, aquestes es classifiquen en dos tipus: les variables locals i les globals.
S’anomenen variables locals a les variables que es declaren dins d’una funció o d’un bloc de codi i el seu àmbit és limitat a aquesta funció o bloc de codi. Aquestes variables no són accessibles fora de la funció o del bloc de codi on han estat declarades.
Les variables locals ajuden a encapsular la lògica de la funció, fent-la més independent i reutilitzable.
Exemple de variable local
En el següent programa es produeix un error perquè la x és una variable local de la funció f(). El programa principal no pot accedir-hi perquè està fora del seu àmbit.
def f(): ''' Funció per a mostrar el comportament d'una variable local Entrada: - Sortida: None ''' x = 10 # Variable local print(x) return # Programa principal f() print(x)
Si executeu el programa obtindreu el següent:
:~/$ python3 variable_local.py 10 Traceback (most recent call last): File ".../variable_local.py", line 13, in <module> print(x) ∧ NameError: name 'x' is not defined
L’àmbit de la variable x està restringit a la funció f(). El programa principal no reconeix cap variable amb aquest nom.
Les variables globals es declaren fora de qualsevol funció o bloc de codi i són accessibles des de qualsevol part del programa. Aquestes variables poden ser utilitzades tant dins com fora de les funcions.
En la majoria dels altres llenguatges de programació, però, les variables definides en el programa principal són variables locals per aquest programa principal, és a dir, no es poden accedir des de dins de les funcions. Però hi ha paraules clau que permeten que aquestes variables siguin globals.
Exemple de variable global
Si executeu el següent programa comprovareu que la variable y és accessible des de l’interior de la funció g()
def g(): ''' Funció per a mostrar el comportament d'una variable local Entrada: res Sortida: None ''' print(y, 'dins de g()') return # Programa principal y = 20 # Variable global g() print(y, 'en el programa principal')
En Python, totes les variables definides en el programa principal són variables globals. El seu àmbit inclou tot el fitxer en el qual han estat definides (incloses les funcions).
Àmbit de les variables (i constants) i programació modular estructurada
Les bones pràctiques en programació estructurada modular limiten l’ús de variables o constants globals al que és absolutament imprescindible, ja que fan que el codi sigui menys comprensible i més propens a errors.
S’han d’utilitzar paràmetres de funció per passar dades a les funcions. S’ha d’evitar que les funcions accedeixin a les constants i variables definides en el programa principal.
Fixeu-vos en el codi següent d’una funció que fa ús d’una constant global:
En aquest exemple es fa ús de la funció print() perquè és un programa que interactua amb l’usuari.
def endevinar(n): ''' Funció que comprova si el num és el guanyador Entrada: int (entre o i 10 inclosos) Sortida: bool ''' return n == premiat MAX_INTENTS = 5 premiat = 3 # programa principal i = 0 num = -1 while num != premiat and i < MAX_INTENTS: num = int(input('Escriu un nombre del 1 al 10\n')) if endevinar(num): print('Has guanyat!') i += 1
La funció endevinar() pot accedir a la variable premiat perquè està definida en el programa principal, de manera que el Python l’entén com a global.
Si copieu la funció endevinar en un altre fitxer despreocupadament, el programa no funcionarà (llevat que hàgiu definit una variable global que s’anomeni premiat).
Les dades han d’arribar a la funció a través dels paràmetres i arguments. Això les fa independents del programa que les crida i les funcions guanyen en llegibilitat i reusabilitat.
Aquí teniu el mateix programa amb dades passades per argument, li passa per alt el nombre guanyador a la funció mitjançant un argument:
def endevinar(n, guanyador): ''' Funció que comprova si el num és el guanyador Entrada: int (entre o i 10 inclosos) Sortida: bool ''' return n == guanyador # programa principal MAX_INTENTS = 5 premiat = 3 i = 0 num = -1 while num != premiat and i < MAX_INTENTS: num = int(input('Escriu un nombre del 1 al 10\n')) if endevinar(num, premiat): print('Has guanyat!') i += 1
Ara el nombre guanyador arriba a la funció a través del paràmetre premiat. El programa el passa com a argument amb la variable guanyador.
Si copieu la funció, es pot utilitzar en un altre programa sense haver de pensar a definir constants de manera obligatòria.
Variables globals i paràmetres
En treballar amb funcions en Python, és comú trobar situacions en què els noms de paràmetres i de les variables del programa principal coincideixen. Python té un mecanisme per evitar les confusions, tot i que sempre és recomanable no utilitzar variables en el programa principal que tinguin el mateix nom que els paràmetres de les funcions a les quals criden.
Aquests conflictes de noms es produeixen quan hi ha un sol nom utilitzat per referir-se a diferents variables o paràmetres en diferents àmbits d’un programa.
Python busca primer el nom dins de l’àmbit local de la funció. Si no el troba, busca a l’àmbit global.
Fixeu-vos en el següent exemple:
def endevinar(n, guanyador): ''' Funció que comprova si el num és el guanyador Entrada: int (entre o i 10 inclosos) Sortida: bool ''' n = n - 1 return n + 1 == guanyador # programa principal MAX_INTENTS = 5 premiat = 3 i = 0 n = -1 while n != premiat and i < MAX_INTENTS: n = int(input('Escriu un nombre del 1 al 10\n')) if endevinar(n, premiat): print('Has guanyat! el nombre premiat és:', n) i += 1
En aquest codi podeu veure un cas en què coexisteixen un paràmetre de la funció endevinar que s’anomena n, i una variable definida en el programa principal (i, per tant, global) amb el mateix nom; tot i que la funció la modifica (la variable n local, quan fa l’assignació n = n -1, la variable global no n’és afectada.
Per resoldre els conflictes de noms, Python busca primer dins de l’àmbit local de la funció. Si no troba la variable, busca en l’àmbit global.
En l’exemple ha trobat la n en l’àmbit local de la funció endevinar(), i tot i que també hi havia una variable global amb el mateix nom, no l’ha modificada perquè ja n’havia trobat una.
No hi ha cap problema amb què diferents funcions comparteixin el mateix nom de paràmetre: en cadascuna d’elles és local i no es produeixen conflictes entre dues funcions que puguin utilitzar un o més paràmetres amb els mateixos noms.
Principi de responsabilitat única (SRP)
En aquest objecte d’aprenentatge, es definirà què és SRP, per què és important i com aplicar-lo en els vostres propis programes utilitzant el llenguatge de programació Python.
Definició d'SRP
Les sigles SRP corresponen, en anglès, a single-responsibility principle. És un principi, SRP va ser definit per l’enginyer de software Robert C. Martin el qual va establir que una classe (funció en el nostre cas) ha de tenir una única raó per canviar.
El Principi de responsabilitat única (SRP) és un principi de disseny de programari que estableix que cada classe o mòdul ha de tenir una sola responsabilitat, és a dir, una única raó per canviar. Aquest principi promou la modularitat i simplifica el manteniment del codi assegurant-se que cada component del sistema tingui un propòsit clar i definit.
L’aplicació de l’SRP al codi té diversos avantatges:
- Manteniment més senzill: el codi és més fàcil de mantenir perquè cada part del programa té una responsabilitat clara i específica.
- Reutilització de codi: les funcions o classes que segueixen l’SRP són més fàcilment reutilitzables en altres parts del programa o en altres programes.
- Depuració més senzilla: quan hi ha un error, és més fàcil de trobar perquè només cal buscar en la part del codi que és responsable d’aquesta funcionalitat.
Exemples d'aplicació de l'SRP
En aquest apartat estudiareu tres exemples per il·lustrar l’aplicació del Principi de responsabilitat única. Els dos primers són senzillets: s’aplicarà el principi sobre un programa que valida un dni, o un altre que escriu les lletres inicials d’un nom.
En el tercer exemple seguireu totes les passes fins a desxifrar el missatge en codi amagat en els colors d’un paracaigudes.
Validar un DNI
Un DNI és correcte si:
- Consta de 9 caràcters.
- Els 8 primers són numèrics.
- El primer no és el 0.
- L’últim caràcter ha de coincidir amb una lletra segons la fórmula següent: fer el mòdul de la part numèrica entre 23 i buscar la lletra que ocupa la posició en una cadena determinada.
És un programa que ja heu fet diverses vegades, però considereu la solució següent:
def valida_dni(cadena): ''' Funció que diu si una cadena és un dni vàlid input: str output: bool ''' LLETRES_DNI = 'TRWAGMYFPDXBNJZSQVHLCKE' # té la longitud correcta if len(cadena) != 9: return False # els 8 primers son dígits if not cadena[:-1].isdigit(): return False # El primer dígit no és el 0 if cadena[0] == '0': return False # l'últim és la lletra corresponent (en majúscules o minúscules) pos = int(cadena[:-1]) % len(LLETRES_DNI) lletra = LLETRES_DNI[pos] if cadena[-1] not in [lletra, lletra.lower()]: return False return True print(valida_dni('12345678Z')) print(valida_dni('12345678z')) print(valida_dni('1234567L')) print(valida_dni('01234567L')) print(valida_dni('1234567UI')) print(valida_dni('123456789')) print(valida_dni('123456789P')) print(valida_dni('dni'))
La funció fa perfectament el que ha de fer i d’entrada no té cap problema. Però què passaria si decidissin modificar la cadena de lletres? El programador hauria d’accedir a la funció i modificar-la, ja que la responsabilitat de calcular la lletra és de la funció.
Què passaria si canviessin les condicions sobre el nombre del DNI, per exemple la llargada o que es permetés començar amb el 0? Aquesta responsabilitat també recau en la funció i també s’hauria de modificar. S’han detectat dos motius diferents que fan que s’hagi de modificar aquesta funció. Segons l’SRP, aquesta funció està mal definida. Cada funció ha de fer una única cosa, ha de ser “atòmica”, per dir-ho d’alguna manera.
Validar un DNI, és una única acció. Però si mentre es valida el DNI també s’ha de calcular la lletra corresponent, aquesta acció necessita una funció per si sola. Si s’ha definit aquesta funció i es treballa amb els DNI, és possible que en algun cas es necessiti calcular la lletra a partir d’un DNI (si algú no se’n recorda, per exemple). Caldrà definir una funció que rebi un DNI numèric i retorni la lletra. En el moment de definir-la es veurà que s’està repetint feina que ja s’havia fet.
Això indica que ha d’haver-hi una funció “més petita” o “més nuclear” que calculi la lletra, i la funció que valida haurà de cridar a aquesta altra funció que fa una feina més específica. D’aquesta manera, la responsabilitat de canviar si es modifica la tira de caràcters és de la funció de calcular la lletra. Però, si es produís algun canvi en el número, seria responsabilitat de l’altra funció. Si es modifiqués la cadena de lletres només caldria modificar la funció lletra_dni(). La funció valida_dni() no necessitaria cap canvi.
El següent disseny s’adequa més a l’SRP i serà molt més fàcil de mantenir si hi ha canvis futurs; la facilitat de manteniment és potser el primer objectiu dels administradors de sistemes.
def lletra_dni(dni): ''' Funció que retorna la lletra del dni input: enter part numèrica del dni) output: str (la lletra) ''' LLETRES_DNI = 'TRWAGMYFPDXBNJZSQVHLCKE' pos = dni % len(LLETRES_DNI) return LLETRES_DNI[pos] def valida_dni(cadena): ''' Funció que diu si una cadena és un dni vàlid input: str output: bool ''' # té la longitud correcta if len(cadena) != 9: return False # els 8 primers son dígits if not cadena[:-1].isdigit(): return False # El primer dígit no és el 0 if cadena[0] == '0': return False # l'últim és la lletra corresponent (en majúscules o minúscules) lletra = lletra_dni(int(cadena[:-1])) if cadena[-1] not in [lletra, lletra.lower()]: return False return True print(valida_dni('12345678Z')) print(valida_dni('12345678z')) print(valida_dni('1234567L')) print(valida_dni('01234567L')) print(valida_dni('1234567UI')) print(valida_dni('123456789')) print(valida_dni('123456789P')) print(valida_dni('dni'))
Mostrar les inicials
El següent exemple que estudiareu serà el programa resultant del següent enunciat: escriviu un programa que llegeixi el nom d’una persona i en mostri les inicials.
Després de fer el joc de proves i tots els passos corresponents, s’ha codificat aquest programa:
def mostra_inicials(nom_complert): ''' Funció que busca les inicials d'un nom i les mostra Entrada: str (nom complert) Sortida: str (ok perquè tot ha anat bé) ''' noms = nom_complert.split() inicials = [] for paraula in noms: inicials.append(paraula[0].upper()) print('.'.join(inicials)) return 'ok' mostra_inicials(input())
La funció mostra_inicials() no acompleix el principi SRP perquè:
- La funció calcula les inicials.
- La funció mostra el valor calculat.
Si es decidís mostrar les inicials en una finestra gràfica, no es podria fer ús d’aquesta funció i, en canvi, l’algorisme del càlcul de les inicials seria el mateix. Això també ho podeu veure en l’exemple que segueix. Es tracta d’un programa que dona resposta a l’enunciat que diu: escriviu un programa que llegeixi el nom de dues persones per l’entrada estàndard i digui si tenen les mateixes inicials.
No es pot utilitzar la funció anteriorment definida, ja que el programa no tindrà accés a la cadena amb les inicials. La solució segons el principi SRP és fer que la funció retorni el que ha calculat (la cadena amb les inicials en aquest cas) i que sigui el programa qui decideixi mostrar quan calgui.
Seguidament, es mostra l’exemple dels dos programes utilitzant la mateixa funció calcular_inicials(). El primer cas és de la funció calcula les inicials i el programa principal s’encarrega de gestionar l’entrada i la sortida:
def calcular_inicials(nom_complet): ''' Funció que calcula les inicials d'un nom Input: str (nom complert) Output: str (les inicials) ''' noms = nom_complet.split() inicials = [] for paraula in noms: inicials.append(paraula[0].upper()) return '.'.join(inicials) # Programa principal print( calcular_inicials(input()))
El següent és el del programa que compara inicials:
def calcular_inicials(nom_complet): ''' Funció que calcula les inicials d'un nom Input: str (nom complert) Output: str (les inicials) ''' noms = nom_complet.split() inicials = [] for paraula in noms: inicials.append(paraula[0].upper()) return '.'.join(inicials) # Programa principal persona1 = input() persona2 = input() if calcular_inicials(persona1) == calcular_inicials(persona2): print('Les inicials són les mateixes') else: print('Les inicials són diferents')
Els dos programes utilitzen la informació que el proporciona la funció calcular_inicials() segons el seu objectiu.
Desxifrar un codi cromàtic
-

- El paracaigudes de l'astromòbil Perseverance.Font: NASA/JPL-Caltech.
El paracaigudes que va ajudar a aterrar l’astromòbil Perseverance a Mart l’any 2021 va revelar un patró de colors aparentment aleatori. Més tard els funcionaris de la NASA van dir que la distribució de colors no era aleatòria sinó que amagava un missatge escrit en codi binari.
L’objectiu d’aquest exemple és fer un programa que ens ajudi a desxifrar el codi binari dels paracaigudes, i entendre l’ús de les funcions i el perquè del principi de la responsabilitat única (SRP).
Resoldre el problema a mà
Seguiu les indicacions de @tanurai on twitter per a desxifrar a mà la primera paraula oculta (la de la rodona concèntrica interior).
En la figura es mostra el procés que s’ha de seguir per a desxifrar el codi binari.
- Unes parts del paracaigudes són de color taronja, i altres són de color blanc.
- Cada bocí de color taronja s’interpretarà com al bit 1, i cada bocí del color blanc com al bit 0. S’interpretarà el codi en grups de 7 bocins.
- Entre cada grup s’hi ha afegit un separador. Un separador són tres bocins de color blanc.
- Cada línia concèntrica representa una paraula. S’ha de traduir cada grup per obtenir el caràcter corresponent, i unint els caràcters s’han d’obtenir les paraules.
- El codi per a desxifrar el grup i convertir-lo a caràcter és el següent: el grup que, en base 2, representa el nombre decimal 1 correspon a la lletra ‘A’, el que representa el 2 correspon a la ‘B’, etc..
- Els grups representats només per uns (o que només contenen bocins taronges), es considera que són bytes que no aporten informació i que no s’han de traduir.
- La circumferència exterior s’ha d’interpretar de manera diferent.
Podeu desxifrar el codi corresponent a la paraula de la circumferència concèntrica interior de la imatge figura.
Si us fixeu en la circumferència interior, començant per la dreta del que està escrit (just després dels 11111111), heu de traduir els colors de quatre conjunts. Són els següents:
- 0000010
- 0000001
- 0010010
- 0000101
A la taula es mostren els passos per cada grup de colors:
| Binari | Decimal | Lletra |
|---|---|---|
| 0000010 | 4 | D |
| 0000001 | 1 | A |
| 0010010 | 18 | R |
| 0000101 | 5 | E |
Fer un joc de proves i les especificacions d’entrada
Com que no sabem com fer-ho perquè les entrades siguin fotos de paracaigudes en codi binari, utilitzeu els símbols B (per a designar un pedaç blanc) i V (per a designar-ne un de vermell). Utilitzeu tres blancs (BBB) per a separar dues lletres, tal com es fa en el paracaigudes.
Les especificacions d’entrada seran: un str que correspon a un missatge en codi parachute correcte. Una prova ha de ser la paraula que heu traduït.
A la taula es mostra un joc de proves del programa:
| Entrada | Sortida |
|---|---|
| BBBBBBBVBBBBBBBBBBBVBBBBBVBBVBBBBBBBBVBV | DARE |
| BBBBBBBBBVBBBBBBBBVB | AB |
Refinament del programa
Aquí hi teniu el refinament dels passos a seguir. Sota cada pas hi ha l’exemple de l’algorisme utilitzat per traduir la primera lletra.
BBBBBBBVBBBBBBBBBBBVBBBBBVBBVBBBBBBBBVBV:
- Retallar els grups per descodificar
BBBBVBB - Passar a binari un grup
0000100 - Passar de binari a decimal
4 - Desxifrar el caràcter
D - Afegir al missatge
Docstrings de les funcions
Per a cada pas del refinament, cal que us pregunteu si cal expandir-lo o no en una funció. La primera pregunta ha de ser sobre el programa general: el descodificador hauria de ser una funció? Sí, donat que fa una feina única i precisa, està molt clar el que rep i també el que ha de retornar. I respecte a les altres línies del refinament:
- Per retallar no cal definir cap funció perquè hi ha el slicing.
- Per passar de colors a binari sí que és necessari utilitzar una funció. Té una responsabilitat única i ha d’estar separada de les altres. Penseu, per exemple, que si l’entrada en comptes de ser un conjunt de B i V fos una fotografia, modificant únicament aquesta funció el programa hauria de funcionar igual (segons SRP). Aquest és un exemple molt clar d’SRP, tot i que és possible que la primera intenció sigui pensar que per modificar B i V a o i 1 no cal cap funció.
- Per passar de codi binari a decimal serà necessària una funció.
- Per desxifrar el caràcter de decimal a lletra també en caldrà una. És possible que penseu que l’algorisme és massa senzill per merèixer una funció. Però és necessària segons el principi SRP: si decidiu modificar el xifratge, utilitzant el nombre del codi ASCII en comptes de la posició en l’alfabet, s’ha de buscar la funció que té aquesta única responsabilitat i modificar-la.
- Afegir la lletra al missatge es pot expandir en el mateix programa.
Els docstrings de les funcions que s’han de definir són els següents:
def traductor(missatge_x): ''' Funció que tradueix un missatge xifrat en codi ‘parachute’ a text. El missatge té separadors entre lletres de longitud 3 i s'escriu emprant per a cada caràcter una cadena de longitud 7. Entrada: str (missatge xifrat segons el codi (correcte)) Sortida: str (Missatge desxifrat) '''
Aquesta serà la funció serà cridada pel programa principal. Rebrà un seguit de B i V corresponents al missatge d’una circumferència del paracaigudes (una paraula), ben configurat segons les normes del que anomenarem codi parachute. Aquesta funció es podria modificar per a rebre directament la fotografia del paracaigudes. En aquest cas, només caldria canviar el tipus d’entrada. Retornarà la paraula traduïda. En l’exemple del paracaigudes haureu de cridar tres vegades la funció (una per a cada circumferència concèntrica) i retornarà DARE, MIGHTY, i THINGS (atreveix-te a fer coses poderoses), respectivament. És la funció principal, ja que el programa principal només la cridarà a ella.
def convertir_a_binari(para): ''' Funció que passa un nombre escrit en parachute binari Entrada: str (Codi B/V) Sortida: str (codi binari) '''
Aquesta és la funció que transcriurà el codi parachute a binari. El codi parachute consisteix en un seguit de B i V, però es podria modificar per una funció diferent que podria rebre una foto del paracaigudes i convertir-la a binari.
S’encarregarà de traduir la cadena BBBBVBB al codi binari 0000100
def baseb_to_deci(num_b, b): ''' Funció que passa un nombre escrit en una base b a decimal Entrada: str, int(num_b: cadena en base b correcta. Lletres minúscules, base major o igual a 2 Sortida: int '''
Aquesta funció ja estava feta. Permetia passar de base b a base 10 i només cal que l’utilitzeu indicant que la base serà el nombre 2. És una funció molt general i que ja heu utilitzat més d’una vegada. Si no teniu aquesta funció, seria més lògic definir-ne una que passi de base 2 a binari, ja que la feina per a ampliar la base per a fer-la molt general és complicada. En l’exemple actual, ens transformarà el nombre binari 0000100 en el nombre decimal 4
Es proposa aquesta funció perquè l’heu utilitzat en altres ocasions. Però es podria utilitzar la funció int() del sistema amb arguments. Comproveu que el resultat d’executar
int(‘0000100’, 2)
és 4.
def desxifrar_car(n): ''' Retorna el caràcter corresponent al codi numèric segons el nostre xifratge En aquest cas, la posició de la lletra majúscula Entrada: int entre 1 i 26 Sortida: str (lletra majúscula corresponent) '''
Aquesta funció relacionarà el nombre decimal amb la lletra que corresponent. En l’exemple que esteu programant, al nombre quatre li assignarà la lletra D.
Es pot pensar que és una funció molt senzilla, i també es pot pensar que no cal definir una funció per a fer aquesta feina. Però és una feina que és una responsabilitat única. Si penseu a canviar aquest codi i utilitzar el codi ASCII per relacionar el nombre decimal amb el caràcter, s’ha de modificar aquesta funció.
En cas que no s’hagués definit aquesta funció i l’operació s’efectués directament dins la funció del traductor, s’hauria de modificar una part del traductor. No seria correcte, ja que els algorismes que utilitza el traductor per a passar de binari a decimal, o per retallar en grups de 7, són independents del tipus de xifratge que es vulgui utilitzar, i no haurien de veure’s afectats per una modificació d’aquest tipus. Això us ha d’indicar que el traductor no segueix el principi SRP perquè a més de la seva feina també té definit el codi utilitzat per a la traducció.
Definició de les funcions
El següent pas serà definir els fitxers amb dada funció i amb el seu test-driver corresponent, començant de les més atòmiques (que no en criden a cap), i anar ascendint per acabar definint la funció del traductor mateix.
El codi de la funció baseb_to_deci(), amb el seu test-driver és el següent:
def baseb_to_deci(num_b, b): ''' Funció que passa un nonbre escrit en una base b a decimal. num_b ha de ser una cadena correcta escrita en la base b (cada xifra ha d'estar entre 0 i b si la base és menor o igual a 10, i s'han d'utlitzar les lletres de l'abcdari per espandir les xifres quan b és major de 10. Input: str (corresponent a una xifra en base b correcta), int Output: int (major o igual a 2) ''' num_b = num_b.upper() #per acceptar en miniscules i majuscules total = 0 exponent = len(num_b) - 1 # per cadascun dels caràcters es calcula el valor for car in num_b: # si és numèric if car >= '0' and car <= '9': total = total + int(car) * b ** exponent # si és una majúscula else: #trampeta total = total + (ord(car) - ord('A') + 10) * b ** exponent exponent = exponent - 1 return total # test-driver print(baseb_to_deci('A0', 16)) print(baseb_to_deci('a0', 16)) print(baseb_to_deci('a0', 20)) print(baseb_to_deci('111100', 2)) print(baseb_to_deci('521', 8)) # si és numèric if car >= '0' and car <= '9': total = total + int(car) * b ** exponent # si és una majúscula else: #trampeta total = total + (ord(car) - ord('A') + 10) * b ** exponent exponent = exponent - 1 return total # test-driver print(baseb_to_deci('A0', 16)) print(baseb_to_deci('a0', 16)) print(baseb_to_deci('a0', 20)) print(baseb_to_deci('111100', 2)) print(baseb_to_deci('521', 8))
El codi de la funció convertir_a_binari() és:
def convertir_a_binari(para): ''' Funció que passa un nombre escrit en parachute binari Entrada: str (Codi B/V) Sortida: str (codi binari) ''' ZERO = 'B' UN = 'V' binari = '' for c in para: if c == ZERO: carac = '0' else: carac = '1' binari = binari + carac return binari print(convertir_a_binari('BBVB')) print(convertir_a_binari('BBVBVB'))
I el de desxifrar_car(/:
def desxifrar_car(n): ''' Retorna el caràcter corresponent al codi numèric segons el nostre xifratge En aquest cas, la posició de la lletra majúscula Entrada: int entre 1 i 26 Sortida: str (lletra majúscula corresponent) ''' LLETRES = '0ABCDEFGHIJKLMNOPQRSTUVWXYZ' #LA A val 1 return LLETRES[n] print(desxifrar_car(1)) print(desxifrar_car(4))
Ara que ja teniu les funcions petites definides i comprovades, cal construir la funció del traductor. Aquesta funció té una única responsabilitat, que és traduir un missatge escrit en un codi concret. Però, per arribar a fer la seva feina, s’ha de servir d’altres funcions més “atòmiques”. Cadascuna d’elles amb la seva responsabilitat única.
En el fitxer de la funció del traductor cal copiar la definició de totes les que són cridades. Però el test-driver no s’ha de repetir perquè ja han estat verificades per separat. El codi de la funció traductor() i test-driver seria el següent:
def baseb_to_deci(num_b, b): ''' Funció que passa un nombre escrit en una base b a decimal Entrada: str, int(num_b: cadena en base b correcta. Lletres minúscules, base major o igual a 2 Sortida: int ''' total = 0 exponent = len(num_b) - 1 for car in num_b: if car >= '0' and car <= '9': total = total + int(car) * b ** exponent else: #trampeta total = total + (ord(car) - ord('a') + 10) * b ** exponent exponent = exponent - 1 return total def convertir_a_binari(para): ''' Funció que passa un nombre escrit en parachute binari Entrada: str (Codi B/V) Sortida: str (codi binari) ''' ZERO = 'B' UN = 'V' binari = '' for c in para: if c == ZERO: carac = '0' else: carac = '1' binari = binari + carac return binari def traductor(missatge_x): ''' Funció que tradueix un missatge xifrat en codi ‘parachute’ a text. El missatge té separadors entre lletres de longitud 3 i s'escriu emprant per a cada caràcter una cadena de longitud 7. Entrada: str (missatge xifrat segons el codi (correcte)) Sortida: Missatge desxifrat ''' LEN_PACK = 7 LEN_SEP = 3 missatge_d = '' llargada = LEN_SEP + LEN_PACK # retallo segons els caràcters for i in range(0, len(missatge_x), llargada): pack = missatge_x[i + LEN_SEP: i + llargada] # passo a binari un set bina = convertir_a_binari(pack) # passo de binari a decimal deci = baseb_to_deci(bina, 2) # desxifro el caràcter car = desxifrar_car(deci) missatge_d = missatge_d + car return missatge_d def desxifrar_car(n): ''' Retorna el caràcter corresponent al codi numèric segons el nostre xifratge En aquest cas, la posició de la lletra majúscula Entrada: int entre 1 i 26 Sortida: str (lletra majúscula corresponent) ''' LLETRES = '0ABCDEFGHIJKLMNOPQRSTUVWXYZ' #LA A val 1 return LLETRES[n] #test-driver print(traductor('BBBBBBBVBBBBBBBBBBBVBBBBBVBBVBBBBBBBBVBV')) print(traductor('BBBBBBBBBVBBBBBBBBVB'))
Codificació del programa
El programa, tal com s’ha plantejat en el joc de proves, llegirà una línia de codi parachute i la traduirà mostrant la paraula amagada. Per a aconseguir-ho, només cal que substituïu el test-driver del fitxer anterior per la lectura d’una línia enviada per l’entrada estàndard.
print(input())
Verificació
Només cal que comproveu que els resultats del joc de proves són els esperats. Per acabar, com ho faríeu per utilitzar el programa per a desxifrar el missatge de les tres circumferències internes del paracaigudes? El que necessiteu és traduir 3 paraules, corresponent a cada circumferència. Primer cal adaptar la fotografia a un fitxer amb l’entrada acceptada per les especificacions d’entrada.
Missatge del paracaigudes:
BBBBBBBVBBBBBBBBBBBVBBBBBVBBVBBBBBBBBVBV BBBBBBVVBVBBBBBBVBBVBBBBBBBVVVBBBBBBVBBBBBBBBVBVBBBBBBBVVBBV BBBBBVBVBBBBBBBBVBBBBBBBBBVBBVBBBBBBVVVBBBBBBBBVVVBBBBBVBBVV
Per a desxifrar el missatge complet, cal que substituïu el programa principal anterior per la lectura de tres línies:
print(traductor(input())) print(traductor(input())) print(traductor(input()))
Ja només falta executar el nou programa redirigint l’entrada estàndard:
$ python3 desxifrar_paracaigudes.py < missatge_paracaigudes.txt DARE MIGHTY THINGS
Conclusió
Si es modifica una de les característiques de la funció del traductor, s’haurà de modificar només una de les funcions menors, sense alterar les altres. Per exemple:
- Si l’entrada és una fotografia en comptes d’una cadena de B i V, la funció a modificar serà
convertir_a_binari(). Les altres no caldrà tocar-les. - Si es decideix modificar el codi de conversió a caràcter, només s’haurà de substituir la funció
esxifrar_car()per una funció amb el nou algorisme que relacioni nombres decimals amb caràcters. - Si es decideix que per relacionar el missatge inicial amb un nombre decimal es vol utilitzar algun algorisme diferent que el del codi binari (per exemple, en hexadecimal), s’hauria de canviar la funció que fa la transformació. En aquest cas, també caldria tocar la funció que llegeix les dades perquè l’entrada no tindria només dos estats sinó setze.
Cada funció té la seva responsabilitat, i només una. Si es modifica un paràmetre d’un programa estructurat segons l’SRP, només cal buscar i modificar la funció que s’encarrega de la responsabilitat modificada.





