Fins ara hem escrit codi que s’executa de dalt a baix, amb bucles i condicions que controlen el flux. Però a mesura que els nostres scripts creixen, ens trobam repetint blocs de codi similars en diversos llocs. Les funcions ens permeten encapsular codi en blocs reutilitzables amb un nom, fent els programes més organitzats, llegibles i fàcils de mantenir.
Raó de ser #
Imaginem que volem mostrar un separador visual en diversos punts del nostre script. Sense funcions repetim el codi cada vegada:
print("=" * 40)
print("Secció 1: Estat del sistema")
print("=" * 40)
# ... codi ...
print("=" * 40)
print("Secció 2: Ús de recursos")
print("=" * 40)
# ... més codi ...
print("=" * 40)
print("Secció 3: Alertes")
print("=" * 40)
# ... i més codi ...Si volem canviar l’amplada del separador de 40 a 50, hem de modificar-ho en tres llocs. Amb una funció:
def mostrar_seccio(titol):
print("=" * 40)
print(titol)
print("=" * 40)
mostrar_seccio("Secció 1: Estat del sistema")
# ... codi ...
mostrar_seccio("Secció 2: Ús de recursos")
# ... més codi ...
mostrar_seccio("Secció 3: Alertes")
# ... i més codi ...Ara el codi és més curt, més clar, i si volem canviar el format només hem de modificar la funció.
Les funcions ens aporten:
- Reutilització, car escrivim el codi una vegada i l’usam moltes.
- Organització, car dividim el programa en blocs amb propòsits clars.
- Mantenibilitat, car els canvis es fan en un sol lloc.
- Llegibilitat, car el codi principal queda més net i fàcil d’entendre.
Definir i cridar #
Definim una funció amb la paraula clau def, seguida del nom, parèntesis i dos punts:
def nom_funcio():
# Codi de la funció
# indentat amb 4 espais
pass
passés una instrucció buida que no fa res.
Per executar la funció, la cridam escrivint el seu nom seguit de parèntesis:
nom_funcio()Exemple:
def mostrar_benvinguda():
print("=" * 40)
print("Benvingut al sistema de monitoratge")
print("=" * 40)
# Cridar la funció
mostrar_benvinguda()Sortida:
========================================
Benvingut al sistema de monitoratge
========================================Convencions de noms
Seguim les mateixes convencions que vàrem adoptar per a les variables:
- Usam
snake_case: paraules en minúscules separades per guions baixos. - Triam noms descriptius que indiquin què fa la funció.
- Solem usar verbs:
mostrar_informe(),calcular_percentatge(),validar_entrada().
Exemples de noms a evitar, car són poc descriptius:
def func1():
pass
def fer_coses():
passParàmetres i arguments #
Les funcions poden rebre informació a través de paràmetres. Els paràmetres es defineixen dins dels parèntesis:
def saludar(nom):
print(f"Hola, {nom}!")
saludar("Anna") # Hola, Anna!
saludar("Pere") # Hola, Pere!Tècnicament parlant:
- Paràmetre: la variable definida a la funció (
noma l’exemple), que s’inicialitza quan cridam la funció. - Argument: el valor que passam quan cridam la funció (
"Anna"i"Pere"a l’exemple).
Podem definir diversos paràmetres separats per comes:
def mostrar_info_servidor(nom, ip, port):
print(f"Servidor: {nom}")
print(f" IP: {ip}")
print(f" Port: {port}")
mostrar_info_servidor("web01", "192.168.1.10", 80)Sortida:
Servidor: web01
IP: 192.168.1.10
Port: 80L’ordre dels arguments ha de coincidir amb l’ordre dels paràmetres.
Exemple pràctic
Anem a revisar un exemple de codi que hem usat en anteriors articles per formatar una mida, però usant funcions:
def formatar_bytes(bytes_valor):
if bytes_valor >= 1024 ** 3:
valor = bytes_valor / (1024 ** 3)
unitat = "GB"
elif bytes_valor >= 1024 ** 2:
valor = bytes_valor / (1024 ** 2)
unitat = "MB"
elif bytes_valor >= 1024:
valor = bytes_valor / 1024
unitat = "KB"
else:
valor = bytes_valor
unitat = "bytes"
print(f"{valor:.2f} {unitat}")
formatar_bytes(1536) # 1.50 KB
formatar_bytes(2621440) # 2.50 MB
formatar_bytes(5368709120) # 5.00 GBRetornar valors #
Les funcions anteriors mostren informació per pantalla, però sovint volem que la funció calculi un valor i ens el retorni per usar-lo després. Per això usam return:
def sumar(a, b):
resultat = a + b
return resultat
total = sumar(5, 3)
print(total) # 8Quan Python troba return, la funció acaba immediatament i retorna el valor especificat.
Vegem un segon exemple, senzill, per calcular el percentatge d’espai usat:
def calcular_percentatge(part, total):
if total == 0:
return 0
return (part / total) * 100
espai_usat = 350
espai_total = 500
percentatge = calcular_percentatge(espai_usat, espai_total)
print(f"Ús de disc: {percentatge:.1f}%") # Ús de disc: 70.0%return vs print
És important entendre la diferència entre return i print().:
print()és una funció integrada que mostra un valor per pantalla però no el retorna.returnés una instrucció reservada que retorna un valor que podem emmagatzemar o usar en expressions.
def amb_print(x):
print(x * 2)
def amb_return(x):
return x * 2
# amb_print mostra el resultat però no el podem usar
resultat1 = amb_print(5) # Mostra: 10
print(resultat1) # None (no ha retornat res)
# amb_return retorna el resultat i el podem usar
resultat2 = amb_return(5) # No mostra res
print(resultat2) # 10
print(resultat2 + 3) # 13Múltiples valors
Podem retornar múltiples valors com una tupla:
def analitzar_llista(llista):
minim = min(llista)
maxim = max(llista)
mitjana = sum(llista) / len(llista)
return minim, maxim, mitjana
valors = [10, 25, 8, 42, 15]
min_value, max_value, avg_value = analitzar_llista(valors)
print(f"Mínim: {min_value}") # Mínim: 8
print(f"Màxim: {max_value}") # Màxim: 42
print(f"Mitjana: {avg_value}") # Mitjana: 20.0Retorn implícit
Si una funció no té return o té return sense valor, retorna None:
def sense_return():
print("Hola")
resultat = sense_return()
print(resultat) # NoneExemple pràctic
De nou, revisem un exemple de codi que hem usat en anteriors articles per comprovar si una cadena es una IP vàlida:
def es_ip_valida(ip):
"""Comprova si una cadena té format d'IP vàlida."""
parts = ip.split(".")
if len(parts) != 4:
return False
for part in parts:
if not part.isdigit():
return False
if not 0 <= int(part) <= 255:
return False
return True
# Ús
print(es_ip_valida("192.168.1.10")) # True
print(es_ip_valida("256.1.2.3")) # False
print(es_ip_valida("192.168.1")) # FalseParàmetres per defecte #
Podem assignar valors per defecte als paràmetres. Si no passam un argument per a aquest paràmetre, s’usa el valor per defecte:
def saludar(nom, salutacio="Hola"):
print(f"{salutacio}, {nom}!")
saludar("Anna") # Hola, Anna!
saludar("Pere", "Bon dia") # Bon dia, Pere!Ordre dels paràmetres
Els paràmetres amb valor per defecte han d’anar després dels paràmetres obligatoris. El següent exemple és correcte perquè la funció connectar() té ambdós paràmetres opcionals (amb valor per defecte) després del paràmetre obligatori host:
def connectar(host, port=22, timeout=30):
print(f"Connectant a {host}:{port} (timeout: {timeout}s)")En canvi, el següent exemple és incorrecte perquè la funció connectar() té el paràmetre obligatori timeout després del paràmetre opcional port:
def connectar(host, port=22, timeout): # SyntaxError
passExemple pràctic
Anem a posar en pràctica l’ús de paràmetres obligatoris i opcionals amb un exemple de funció de representació d’una configuració:
def mostrar_taula(dades, titol="Servidor", amplada=40, separador="-"):
"""Mostra dades en format de taula."""
print(separador * amplada)
print(titol.center(amplada))
print(separador * amplada)
for clau, valor in dades.items():
print(f" {clau}: {valor}")
print(separador * amplada)
print()
servidor = {"nom": "web01", "ip": "192.168.1.10", "port": 80}
# Amb valors per defecte
mostrar_taula(servidor)
# Amb paràmetres personalitzats
mostrar_taula(servidor, titol="Proxy", amplada=30, separador="=")Sortida:
----------------------------------------
Servidor
----------------------------------------
nom: web01
ip: 192.168.1.10
port: 80
----------------------------------------
==============================
Proxy
==============================
nom: web01
ip: 192.168.1.10
port: 80
==============================Arguments amb nom #
Quan cridam una funció, podem passar els arguments pel seu nom en lloc de la posició. Aquests arguments es coneixen com a paràmetres nombrats, o keyword arguments. Per exemple, donada la següent funció:
def crear_usuari(nom, uid, shell, home):
print(f"Creant usuari {nom} (UID: {uid})")
print(f" Shell: {shell}")
print(f" Home: {home}")Podem passar els paràmetres per posició, recordant l’ordre:
crear_usuari("anna", 1001, "/bin/bash", "/home/anna")O els podem passar per nom, més clar i on l’ordre no importa:
crear_usuari(
nom="pere",
uid=1002,
home="/home/pere",
shell="/bin/zsh"
)Combinar arguments posicionals i amb nom
Podem combinar els arguments posicionals i els arguments amb nom en una funció, però els posicionals han d’anar primer:
def connectar(host, port, timeout=30, ssl=False):
print(f"Host: {host}, Port: {port}, Timeout: {timeout}, SSL: {ssl}")
# Correcte
connectar("exemple.com", 443, ssl=True)
connectar("exemple.com", 443, timeout=60, ssl=True)
# Incorrecte
connectar(host="exemple.com", 443) # SyntaxErrorExemple pràctic
La següent funció ens ofereix un interessant nivell de flexibilitat a l’hora d’enviar alertes:
def enviar_alerta(
missatge,
nivell="INFO",
destinatari="admin@exemple.com",
urgent=False):
"""Envia una alerta amb diferents nivells de prioritat."""
prefix = "URGENT: " if urgent else ""
print(f"[{nivell}] {prefix}{missatge}")
print(f" Enviant a: {destinatari}")
# Diferents maneres de cridar-la
enviar_alerta("Sistema operatiu")
enviar_alerta("Espai en disc baix", nivell="WARNING")
enviar_alerta("Servei caigut", nivell="ERROR", urgent=True)
enviar_alerta("Backup completat", destinatari="backups@exemple.com")Paràmetres variables #
A vegades volem que una funció accepti un nombre variable d’arguments.
Arguments posicionals variables
El paràmetre *args recull tots els arguments posicionals extra en una tupla. La paraula de referència args vol dir arguments i no és més que una convenció; el que importa és l’asterisc. Podem usar el nom de paràmetre o variable que vulguem. En el següent exemple usam nombres en comptes d’args:
def sumar_tots(*nombres):
total = 0
for n in nombres:
total += n
return total
print(sumar_tots(1, 2)) # 3
print(sumar_tots(1, 2, 3, 4, 5)) # 15
print(sumar_tots(10)) # 10Vegem un altre exemple amb una funció de logging:
def log(*missatges, nivell="INFO"):
prefix = f"[{nivell}]"
text = " ".join(str(m) for m in missatges)
print(f"{prefix} {text}")
log("Servidor", "iniciat")
log("Port", 8080, "obert")
log("Error", "de", "connexió", nivell="ERROR")Sortida:
[INFO] Servidor iniciat
[INFO] Port 8080 obert
[ERROR] Error de connexióPodem combinar l’ús de
*argsi paràmetres posicionals:def f(*args, x, y).
Arguments amb nom variables
El paràmetre **kwargs recull tots els arguments amb nom extra en un diccionari, on les claus són els noms dels paràmetres i els valors són els arguments o valors proporcionats. La paraula de referència kwargs vol dir keyword arguments i no és més que una convenció; el que importa és el doble asterisc. Podem usar el nom de paràmetre o variable que vulguem.
En el següent exemple usam opcions en comptes de kwargs:
def mostrar_config(**opcions):
print("Configuració:")
for clau, valor in opcions.items():
print(f" {clau}: {valor}")
mostrar_config(host="localhost", port=8080, debug=True)Sortida:
Configuració:
host: localhost
port: 8080
debug: TrueCombinar paràmetres normals amb *args i **kwargs
Podem combinar paràmetres normals amb *args i **kwargs, però hem de respectar el següent ordre:
- Paràmetres normals.
*args.- Paràmetres amb valor per defecte.
**kwargs
Per exemple:
def configurar_servei(nom, *ports, actiu=True, **opcions):
print(f"Servei: {nom}")
print(f"Ports: {ports}")
print(f"Actiu: {actiu}")
print(f"Opcions: {opcions}")
configurar_servei("web", 80, 443, timeout=30, max_conn=100)Sortida:
Servei: web
Ports: (80, 443)
Actiu: True
Opcions: {'timeout': 30, 'max_conn': 100}Exemple pràctic
Vegem un exemple pràctic de funció amb paràmetres variables:
def configurar_servei(nom, *ports, **opcions):
"""
Configura un servei amb ports i opcions variables.
:param nom: Nom del servei.
:param ports: Ports on escoltarà (nombre variable).
:param opcions: Opcions addicionals (clau=valor).
"""
print(f"Configurant servei: {nom}")
# Mostrar ports
if ports:
print(f" Ports: {', '.join(str(p) for p in ports)}")
else:
print(" Ports: cap especificat")
# Mostrar opcions
if opcions:
print(" Opcions:")
for clau, valor in opcions.items():
print(f" {clau}: {valor}")
else:
print(" Opcions: cap especificada")
# Diferents maneres de cridar la funció
configurar_servei("nginx")
print()
configurar_servei("apache", 80, 443)
print()
configurar_servei("postgresql", 5432, timeout=30, max_connections=100, ssl=True)Sortida:
Configurant servei: nginx
Ports: cap especificat
Opcions: cap especificada
Configurant servei: apache
Ports: 80, 443
Opcions: cap especificada
Configurant servei: postgresql
Ports: 5432
Opcions:
timeout: 30
max_connections: 100
ssl: TrueDocstrings #
Un docstring és una cadena de text que documenta què fa una funció. Es posa just després de la línia def:
def calcular_percentatge(part, total):
"""Calcula el percentatge que representa 'part' respecte a 'total'."""
if total == 0:
return 0
return (part / total) * 100Format recomanat
Per a funcions més complexes, és útil documentar els paràmetres i el valor de retorn:
def formatar_mida(bytes_valor, decimals=2):
"""
Converteix bytes a una cadena amb la unitat adequada.
:param bytes_valor: Mida en bytes a convertir.
:param decimals: Nombre de decimals a mostrar (per defecte 2).
:return: Cadena formatada amb la mida i la unitat.
Exemple:
>>> formatar_mida(1536)
'1.50 KB'
>>> formatar_mida(5368709120, decimals=0)
'5 GB'
"""
unitats = ["bytes", "KB", "MB", "GB", "TB"]
for unitat in unitats:
if bytes_valor < 1024:
return f"{bytes_valor:.{decimals}f} {unitat}"
bytes_valor /= 1024
return f"{bytes_valor:.{decimals}f} PB"És una bona pràctica usar reStructuredText1 (reST) dins el docstring per a donar format al docstring. Els atributs :param: i :return: s’usen per a indicar el tipus i propòsit de cada paràmetre i el tipus retornat per la funció, respectivament.
Accedir a la documentació
Podem veure el docstring d’una funció amb help() o accedint a l’atribut __doc__:
>>> help(formatar_mida)
Help on function formatar_mida in module __main__:
formatar_mida(bytes_valor, decimals=2)
Converteix bytes a una cadena amb la unitat adequada.
:param bytes_valor: Mida en bytes a convertir.
...
>>> print(formatar_mida.__doc__)
Converteix bytes a una cadena amb la unitat adequada.
...Àmbit de les variables #
L’àmbit o scope d’una variable determina on és visible i accessible. Python té dos àmbits principals:
- Variables locals.
- Variables globals.
Variables locals
Les variables creades dins una funció són locals, és a dir, només existeixen dins la funció. Exemple:
def exemple():
variable_local = 10
print(variable_local)
exemple() # 10
print(variable_local) # NameErrorVariables globals
Les variables creades fora de qualsevol funció són globals, és a dir, són visibles a tot el mòdul. Exemple:
variable_global = "Hola"
def exemple():
print(variable_global) # Pot llegir variables globals
exemple() # Hola
print(variable_global) # HolaModificar variables globals
Si intentam modificar una variable global dins una funció, Python crea una variable local nova:
comptador = 0
def incrementar():
# Error! Python pensa que és local
# però no té valor inicial
comptador = comptador + 1
incrementar() # UnboundLocalErrorPodem usar global per indicar que volem modificar la variable global:
comptador = 0
def incrementar():
global comptador
comptador = comptador + 1
incrementar()
print(comptador) # 1global. El codi és més bo de seguir i depurar quan passam valors com a paràmetres i retornam resultats.
Alternativa: passar i retornar
En comptes d’usar variables globals, és millor passar valors com a paràmetre i retornar resultats. Exemple:
def incrementar(comptador):
return comptador + 1
valor = 0
valor = incrementar(valor)
print(valor) # 1
valor = incrementar(valor)
print(valor) # 2Bones pràctiques #
En aquesta secció veurem quatre bones pràctiques que ens ajuden a escriure codi més robust i mantenible:
- Funcions petites i amb un sol propòsit.
- Noms descriptius.
- Evitar efectes secundaris.
- Clàusules de guarda (early return)
Funcions petites i amb un sol propòsit
Cada funció hauria de fer una sola cosa i fer-la bé. Exemple de funció que fa massa coses:
def processar_servidor(nom, ip, port):
# Valida les dades
# Es connecta al servidor
# Comprova l'estat
# Genera un informe
# Envia una alerta si cal
passÉs millor definir funcions separades:
def validar_dades(nom, ip, port):
pass
def connectar(ip, port):
pass
def comprovar_estat(connexio):
pass
def generar_informe(estat):
pass
def enviar_alerta(informe):
passNoms descriptius
El nom de la funció hauria d’indicar clarament què fa. Exemples:
def processar_fitxer_log(ruta):
pass
def calcular_espai_lliure(disc):
pass
def es_usuari_autoritzat(nom_usuari):
passEvitar efectes secundaris
Una funció hauria de fer el que diu el seu nom i poca cosa més. Els efectes secundaris, e.g., modificar variables globals, imprimir, escriure fitxers, etc., haurien de ser explícits. Per exemple, en comptes de modificar la variable global usuaris, la següent funció rep tant la llista com el nou nom d’usuari i en retorna el resultat:
usuaris = []
def afegir_usuari(llista_usuaris, nom):
return llista_usuaris + [nom]
usuaris = afegir_usuari(usuaris, "anna")Clàusules de guarda
Les clàusules de guarda són comprovacions condicionals gestionen els casos especials al principi per evitar niuaments (early return). Per exemple, donada la següent funció amb niuaments:
def validar(dades):
... # Codi que valida les dades
def processar(dades):
if dades is not None:
if len(dades) > 0:
if validar(dades):
... # Codi que processa les dades
return resultat
return None # Algun if no es compleixAmb clàusules de guarda ens queda un codi més clar i eficient:
def processar(dades):
if dades is None:
return None
if len(dades) == 0:
return None
if not validar(dades):
return None
... # Codi que processa les dades
return resultatExercicis #
Es proposen quatre exercicis pràctics per consolidar els conceptes d’aquest article.
Exercici 1 #
Generador de contrasenyes
Objectiu: Crear funcions per generar contrasenyes aleatòries amb diferents nivells de complexitat.
- Crea un script anomenat
generador_contrasenyes.py. - Implementa una funció
generar_contrasenya()que accepti:longitud: nombre de caràcters (per defecte 12)majuscules: si inclou majúscules (per defecte True)numeros: si inclou números (per defecte True)simbols: si inclou símbols (per defecte False)
- Implementa una funció
avaluar_fortalesa()que retorni “feble”, “mitjana” o “forta” segons la longitud i varietat de caràcters. - Documenta les funcions amb docstrings complets.
- Genera diverses contrasenyes amb diferents configuracions per demostrar l’ús.
Pista: usa el mòdul
randomi la funciórandom.choice()per triar caràcters aleatoris d’una cadena.
Resposta
Exemple d’script generador_contrasenyes.py:
#!/usr/bin/env python3
"""
Generador de contrasenyes amb opcions de complexitat.
Permet generar contrasenyes aleatòries amb control sobre
els tipus de caràcters inclosos i avaluar-ne la fortalesa.
"""
import random
import string
def generar_contrasenya(longitud=12, majuscules=True, numeros=True, simbols=False):
"""
Genera una contrasenya aleatòria.
:param longitud: Nombre de caràcters (per defecte 12).
:param majuscules: Inclou majúscules (per defecte True).
:param numeros: Inclou números (per defecte True).
:param simbols: Inclou símbols (per defecte False).
:return: Contrasenya generada.
"""
# Construir el conjunt de caràcters disponibles
caracters = string.ascii_lowercase # Sempre incloem minúscules
if majuscules:
caracters += string.ascii_uppercase
if numeros:
caracters += string.digits
if simbols:
caracters += "!@#$%^&*()-_=+[]{}|;:,.<>?"
# Generar la contrasenya
contrasenya = ""
for _ in range(longitud):
contrasenya += random.choice(caracters)
return contrasenya
def avaluar_fortalesa(contrasenya):
"""
Avalua la fortalesa d'una contrasenya.
:param contrasenya: Contrasenya a avaluar.
:return: "feble", "mitjana" o "forta".
Criteris:
- Feble: menys de 8 caràcters o només un tipus de caràcter.
- Mitjana: 8-11 caràcters amb almenys 2 tipus de caràcters.
- Forta: 12+ caràcters amb almenys 3 tipus de caràcters.
"""
longitud = len(contrasenya)
# Comptar tipus de caràcters presents
te_minuscules = any(c in string.ascii_lowercase for c in contrasenya)
te_majuscules = any(c in string.ascii_uppercase for c in contrasenya)
te_numeros = any(c in string.digits for c in contrasenya)
te_simbols = any(c in "!@#$%^&*()-_=+[]{}|;:,.<>?" for c in contrasenya)
tipus_caracters = sum([te_minuscules, te_majuscules, te_numeros, te_simbols])
# Avaluar fortalesa
if longitud < 8 or tipus_caracters < 2:
return "feble"
elif longitud >= 12 and tipus_caracters >= 3:
return "forta"
else:
return "mitjana"
def mostrar_contrasenya(contrasenya):
"""Mostra una contrasenya amb la seva avaluació."""
fortalesa = avaluar_fortalesa(contrasenya)
print(f" {contrasenya} ({len(contrasenya)} cars.) és {fortalesa}.")
if __name__ == "__main__":
print("=== Generador de contrasenyes ===")
print()
print("Contrasenyes per defecte (12 cars., majúscules i números):")
for _ in range(3):
pwd = generar_contrasenya()
mostrar_contrasenya(pwd)
print()
print("Contrasenyes curtes (8 cars., només minúscules):")
for _ in range(3):
pwd = generar_contrasenya(longitud=8, majuscules=False, numeros=False)
mostrar_contrasenya(pwd)
print()
print("Contrasenyes fortes (16 cars., amb símbols):")
for _ in range(3):
pwd = generar_contrasenya(longitud=16, simbols=True)
mostrar_contrasenya(pwd)
print()
print("Contrasenyes molt llargues (24 cars., tots els caràcters):")
for _ in range(3):
pwd = generar_contrasenya(longitud=24, simbols=True)
mostrar_contrasenya(pwd)Exercici 2 #
Validador d’entrada
Objectiu: Crear una funció que validi i converteixi entrada de l’usuari.
- Crea un script anomenat
validador.py. - Implementa una funció
llegir_enter()que:- Demani un nombre a l’usuari
- Validi que sigui un enter
- Accepti un rang opcional (mínim, màxim)
- Accepti un valor per defecte si l’usuari prem Enter
- Reintenti si l’entrada no és vàlida
- Usa la funció per demanar un port (1-65535) i un timeout (per defecte 30).
Pista: usa un bucle
whiledins la funció per reintentar.
Resposta
Exemple d’script validador.py:
#!/usr/bin/env python3
"""Funcions per validar entrada de l'usuari."""
def llegir_enter(prompt, minim=None, maxim=None, defecte=None):
"""
Demana un nombre enter a l'usuari amb validació.
:param prompt: Missatge a mostrar.
:param minim: Valor mínim permès (opcional).
:param maxim: Valor màxim permès (opcional).
:param defecte: Valor per defecte si l'usuari no escriu res (opcional).
:return: El nombre enter validat.
"""
while True:
# Construir el prompt amb informació addicional
prompt_complet = prompt
if defecte is not None:
prompt_complet += f" [{defecte}]"
prompt_complet += ": "
entrada = input(prompt_complet).strip()
# Valor per defecte
if entrada == "" and defecte is not None:
return defecte
# Validar que sigui un enter
if not entrada.lstrip("-").isdigit():
print("Error: has d'introduir un nombre enter.")
continue
valor = int(entrada)
# Validar rang
if minim is not None and valor < minim:
print(f"Error: el valor ha de ser almenys {minim}.")
continue
if maxim is not None and valor > maxim:
print(f"Error: el valor ha de ser com a màxim {maxim}.")
continue
return valor
def llegir_si_no(prompt, defecte=True):
"""
Demana una resposta sí/no a l'usuari.
:param prompt: Missatge a mostrar.
:param defecte: Valor per defecte (True = sí, False = no).
:return: True o False.
"""
opcio_defecte = "S/n" if defecte else "s/N"
while True:
entrada = input(f"{prompt} [{opcio_defecte}]: ").strip().lower()
if entrada == "":
return defecte
if entrada in ["s", "sí", "si", "yes", "y"]:
return True
if entrada in ["n", "no"]:
return False
print("Error: respon 's' o 'n'.")
# Exemple d'ús
if __name__ == "__main__":
print("=== Configuració de connexió ===")
print()
port = llegir_enter("Port", minim=1, maxim=65535)
timeout = llegir_enter("Timeout (segons)", minim=1, maxim=300, defecte=30)
ssl = llegir_si_no("Usar SSL?", defecte=True)
print()
print("Configuració seleccionada:")
print(f" Port: {port}")
print(f" Timeout: {timeout}s")
print(f" SSL: {'Sí' if ssl else 'No'}")Exercici 3 #
Calculadora de temps d’execució
Objectiu: Crear funcions per treballar amb durades de processos i tasques.
- Crea un script anomenat
temps_execucio.py. - Implementa una funció
segons_a_hms(segons)que retorni una tupla (hores, minuts, segons). - Implementa una funció
formatar_duracio(segons, format)que accepti:segons: durada en segonsformat: “complet” (2h 15m 30s), “curt” (02:15:30) o “paraules” (2 hores, 15 minuts i 30 segons)
- Implementa una funció
sumar_durades(*durades)que accepti múltiples durades en segons i retorni el total formatat. - Prova les funcions amb exemples de temps d’execució de tasques del sistema.
Pista: usa divisió entera
//i mòdul%per separar hores, minuts i segons.
Resposta
Exemple d’script temps_execucio.py:
#!/usr/bin/env python3
"""
Calculadora de temps d'execució.
Funcions per convertir, formatar i sumar durades de temps,
útils per analitzar logs i temps d'execució de processos.
"""
def segons_a_hms(segons):
"""
Converteix segons a hores, minuts i segons.
:param segons: Durada en segons (enter o decimal).
:return: Tupla (hores, minuts, segons).
Exemple:
>>> segons_a_hms(3725)
(1, 2, 5)
"""
segons = int(segons)
hores = segons // 3600
minuts = (segons % 3600) // 60
secs = segons % 60
return hores, minuts, secs
def formatar_duracio(segons, format="complet"):
"""
Formata una durada en segons segons el format especificat.
:param segons: Durada en segons.
:param format: Tipus de format:
- "complet": 2h 15m 30s
- "curt": 02:15:30
- "paraules": 2 hores, 15 minuts i 30 segons
:return: Cadena formatada.
"""
hores, minuts, secs = segons_a_hms(segons)
if format == "complet":
parts = []
if hores > 0:
parts.append(f"{hores}h")
if minuts > 0:
parts.append(f"{minuts}m")
if secs > 0 or not parts:
parts.append(f"{secs}s")
return " ".join(parts)
elif format == "curt":
return f"{hores:02d}:{minuts:02d}:{secs:02d}"
elif format == "paraules":
parts = []
if hores > 0:
parts.append(f"{hores} {'hora' if hores == 1 else 'hores'}")
if minuts > 0:
parts.append(f"{minuts} {'minut' if minuts == 1 else 'minuts'}")
if secs > 0 or not parts:
parts.append(f"{secs} {'segon' if secs == 1 else 'segons'}")
if len(parts) == 1:
return parts[0]
elif len(parts) == 2:
return f"{parts[0]} i {parts[1]}"
else:
return f"{parts[0]}, {parts[1]} i {parts[2]}"
else:
raise ValueError(f"Format desconegut: {format}")
def sumar_durades(*durades):
"""
Suma múltiples durades i retorna el total formatat.
:param durades: Durades en segons (nombre variable d'arguments).
:return: Diccionari amb el total en segons i en diferents formats.
Exemple:
>>> sumar_durades(120, 3600, 45)
{'segons': 3765, 'complet': '1h 2m 45s', ...}
"""
total = sum(durades)
return {
"segons": total,
"complet": formatar_duracio(total, "complet"),
"curt": formatar_duracio(total, "curt"),
"paraules": formatar_duracio(total, "paraules"),
}
if __name__ == "__main__":
print("=== Calculadora de temps d'execució ===")
print()
# Exemple 1: Conversió bàsica
print("Conversió de segons a hores:minuts:segons:")
for segons in [45, 125, 3725, 7384, 86400]:
h, m, s = segons_a_hms(segons)
print(f" {segons:6} segons = {h}h {m}m {s}s")
print()
# Exemple 2: Diferents formats
print("Diferents formats per a 8145 segons:")
temps = 8145
print(f" Complet: {formatar_duracio(temps, 'complet')}")
print(f" Curt: {formatar_duracio(temps, 'curt')}")
print(f" Paraules: {formatar_duracio(temps, 'paraules')}")
print()
# Exemple 3: Temps d'execució de tasques
print("Temps d'execució de tasques del sistema:")
tasques = [
("Backup base de dades", 1847),
("Compressió de logs", 423),
("Sincronització remota", 2156),
("Neteja de temporals", 67),
]
for nom, segons in tasques:
print(f" {nom}: {formatar_duracio(segons, 'complet')}")
print()
# Exemple 4: Suma de durades
print("Temps total de totes les tasques:")
durades = [t[1] for t in tasques]
total = sumar_durades(*durades)
print(f" Total: {total['segons']} segons")
print(f" Format complet: {total['complet']}")
print(f" Format curt: {total['curt']}")
print(f" Format paraules: {total['paraules']}")
print()
# Exemple 5: Casos especials
print("Casos especials:")
print(f" 0 segons: {formatar_duracio(0, 'paraules')}")
print(f" 1 segon: {formatar_duracio(1, 'paraules')}")
print(f" 60 segons: {formatar_duracio(60, 'paraules')}")
print(f" 3600 segons: {formatar_duracio(3600, 'paraules')}")Exercici 4 #
Validador de configuració de xarxa
Objectiu: Crear un conjunt de funcions per validar paràmetres de xarxa.
- Crea un script anomenat
validador_xarxa.py. - Implementa les funcions següents:
es_ip_valida(ip): comprova si una adreça IPv4 és vàlida.es_mascara_valida(mascara): comprova si una màscara de xarxa és vàlida.es_port_valid(port, permetre_privilegiats): comprova si un port és vàlid, amb opció per bloquejar ports privilegiats.
- Implementa una funció
validar_configuracio(**params)que accepti paràmetres variables (ip, mascara, gateway, dns, port) i retorni un diccionari amb el resultat de cada validació. - Mostra exemples amb configuracions vàlides i invàlides.
Pista: una màscara de xarxa vàlida, en binari, és una seqüència d’uns seguits d’una seqüència de zeros (e.g., 255.255.255.0 = 11111111.11111111.11111111.00000000).
Resposta
Exemple d’script validador_xarxa.py:
#!/usr/bin/env python3
"""
Validador de configuració de xarxa.
Funcions per validar adreces IP, màscares de xarxa,
ports i configuracions completes.
"""
def es_ip_valida(ip):
"""
Comprova si una adreça IPv4 és vàlida.
:param ip: Adreça IP com a cadena (e.g., "192.168.1.1").
:return: True si és vàlida, False altrament.
"""
if not isinstance(ip, str):
return False
parts = ip.split(".")
if len(parts) != 4:
return False
for part in parts:
# Ha de ser un nombre
if not part.isdigit():
return False
# Ha d'estar entre 0 i 255
valor = int(part)
if valor < 0 or valor > 255:
return False
# No pot tenir zeros a l'esquerra (excepte "0")
if len(part) > 1 and part[0] == "0":
return False
return True
def es_mascara_valida(mascara):
"""
Comprova si una màscara de xarxa és vàlida.
Una màscara vàlida, en binari, és una seqüència d'uns
seguida d'una seqüència de zeros.
:param mascara: Màscara com a cadena (e.g., "255.255.255.0").
:return: True si és vàlida, False altrament.
"""
# Primer ha de ser una IP vàlida
if not es_ip_valida(mascara):
return False
# Convertir a binari (32 bits)
parts = mascara.split(".")
binari = ""
for part in parts:
binari += format(int(part), "08b")
# Ha de ser uns seguits de zeros (no pot haver-hi 01)
trobat_zero = False
for bit in binari:
if bit == "0":
trobat_zero = True
elif trobat_zero:
# Un "1" després d'un "0" no és vàlid
return False
return True
def es_port_valid(port, permetre_privilegiats=True):
"""
Comprova si un port és vàlid.
:param port: Número de port.
:param permetre_privilegiats: Si permet ports < 1024 (per defecte True).
:return: True si és vàlid, False altrament.
"""
# Ha de ser un enter
if not isinstance(port, int):
return False
# Ha d'estar en el rang vàlid
if port < 1 or port > 65535:
return False
# Comprovar ports privilegiats
if not permetre_privilegiats and port < 1024:
return False
return True
def validar_configuracio(**params):
"""
Valida una configuració de xarxa completa.
:param params: Paràmetres de xarxa (ip, mascara, gateway, dns, port).
:return: Diccionari amb el resultat de cada validació.
Exemple:
>>> validar_configuracio(ip="192.168.1.10", port=8080)
{'ip': {'valor': '192.168.1.10', 'valid': True}, ...}
"""
resultats = {}
for clau, valor in params.items():
resultat = {"valor": valor, "valid": False, "error": None}
if clau in ["ip", "gateway", "dns"]:
if es_ip_valida(valor):
resultat["valid"] = True
else:
resultat["error"] = "Adreça IP no vàlida"
elif clau == "mascara":
if es_mascara_valida(valor):
resultat["valid"] = True
else:
resultat["error"] = "Màscara de xarxa no vàlida"
elif clau == "port":
permetre = params.get("permetre_privilegiats", True)
if es_port_valid(valor, permetre):
resultat["valid"] = True
else:
if valor < 1 or valor > 65535:
resultat["error"] = "Port fora de rang (1-65535)"
else:
resultat["error"] = "Port privilegiat no permès"
elif clau == "permetre_privilegiats":
# Paràmetre auxiliar, no cal validar
continue
else:
resultat["error"] = f"Paràmetre desconegut: {clau}"
resultats[clau] = resultat
return resultats
def mostrar_validacio(resultats):
"""Mostra els resultats de validació de manera formatada."""
print("Resultats de la validació:")
print("-" * 50)
tot_valid = True
for clau, info in resultats.items():
icona = "✓" if info["valid"] else "✗"
estat = "vàlid" if info["valid"] else info["error"]
print(f" {icona} {clau}: {info['valor']} ({estat})")
if not info["valid"]:
tot_valid = False
print("-" * 50)
if tot_valid:
print("✓ Configuració vàlida")
else:
print("✗ Configuració amb errors")
if __name__ == "__main__":
print("=== Validador de configuració de xarxa ===")
print()
# Exemple 1: Configuració vàlida
print("Exemple 1: Configuració vàlida")
resultats = validar_configuracio(
ip="192.168.1.100",
mascara="255.255.255.0",
gateway="192.168.1.1",
dns="8.8.8.8",
port=8080
)
mostrar_validacio(resultats)
print()
# Exemple 2: Configuració amb errors
print("Exemple 2: Configuració amb errors")
resultats = validar_configuracio(
ip="192.168.1.300", # IP invàlida
mascara="255.255.128.0", # Màscara vàlida
gateway="192.168.1", # Gateway invàlid
port=80,
permetre_privilegiats=False # Port privilegiat no permès
)
mostrar_validacio(resultats)
print()
# Exemple 3: Validacions individuals
print("Exemple 3: Validacions individuals")
print()
ips_a_validar = [
"192.168.1.1",
"10.0.0.1",
"256.1.2.3",
"192.168.1",
"192.168.01.1",
]
print("Validació d'IPs:")
for ip in ips_a_validar:
resultat = "✓ vàlida" if es_ip_valida(ip) else "✗ invàlida"
print(f" {ip:18} {resultat}")
print()
mascares_a_validar = [
"255.255.255.0",
"255.255.0.0",
"255.255.255.128",
"255.255.128.128", # Invàlida
"255.0.255.0", # Invàlida
]
print("Validació de màscares:")
for mascara in mascares_a_validar:
resultat = "✓ vàlida" if es_mascara_valida(mascara) else "✗ invàlida"
print(f" {mascara:18} {resultat}")
print()
ports_a_validar = [
(22, True),
(22, False),
(8080, True),
(8080, False),
(70000, True),
]
print("Validació de ports:")
for port, permetre in ports_a_validar:
resultat = "✓ vàlid" if es_port_valid(port, permetre) else "✗ invàlid"
mode = "amb privilegiats" if permetre else "sense privilegiats"
print(f" Port {port:5} ({mode:18}) {resultat}")Resum #
En aquest article hem après a organitzar el codi en funcions reutilitzables:
- Definim funcions amb
def nom():i les cridam ambnom(). - Els paràmetres permeten passar informació a les funcions; els arguments són els valors que passam.
- La instrucció
returnretorna un valor (o múltiples com a tupla) i acaba la funció. - Els paràmetres per defecte fan les funcions més flexibles.
- Els arguments amb nom milloren la llegibilitat quan hi ha molts paràmetres.
- Les construccions sintáctiques especials
*argsi**kwargspermeten funcions amb nombre variable d’arguments. - Els docstrings documenten què fa la funció, els seus paràmetres i el valor de retorn.
- Les variables tenen un àmbit (scope): local dins funcions, global fora. Evitam modificar variables globals.
- Seguim bones pràctiques: funcions petites, amb un únic propòsit, amb noms descriptius i sense efectes secundaris.