Funciones en Python — ejercicios con notas, un mini banco y dados con estadísticas (y el return que se rindió en la primera tirada)
Preparando el juego de dados de este artículo, quise añadir un aviso al final: «¿ha salido algún doble en la partida?». Escribí una función hay_doble en un minuto, la probé con la partida de ejemplo… y me dijo False. Con un 6 y un 6 en la tercera tirada, delante de mis ojos. Revisé la condición, revisé los datos, añadí prints… y el fallo era una sola palabra mal colocada: un return False dentro de un else, que hacía que la función se rindiera en la primera tirada que no era doble. Lo tienes tal cual en el apartado «¿Qué falla aquí?», porque es de esos errores que no dan ningún mensaje.
Ya viste la teoría de funciones en Python y los tres programas de la práctica. Ahora te toca a ti: cuatro ejercicios en tres niveles (un conversor de notas, un informe, un mini banco y un juego de dados con estadísticas), tres fragmentos con un fallo escondido y, al final, un chuletario para tenerlo todo a mano. El hilo que los une es el error más silencioso del tema: print donde hacía falta return, que te deja trabajando con None sin darte cuenta.
Tabla de Contenidos
Cómo usar este artículo
- Lee el enunciado y la salida esperada. Tu programa tiene que dar exactamente esa salida (en los dos con azar, gracias a una semilla fija).
- Intenta resolverlo en VS Code o en Thonny sin mirar nada más. Si llevas más de 10 minutos atascado, lee la pista.
- Antes de ver las soluciones, haz el apartado «¿Qué falla aquí?»: predice qué sale y comprueba.
- Compara con la solución comentada. No basta con que funcione: mira por qué está hecha así y qué error daría la alternativa obvia.
Ninguno usa input(): los datos van escritos en el programa, como en la práctica. Así puedes comparar tu salida con la del artículo línea a línea.
Nivel básico
Ejercicio 1: conversor de notas entre sistemas
Escribe cuatro funciones que conviertan notas entre tres sistemas: el español (0-10), el americano (0-100) y el de letras (A, B, C, D, F):
espanola_a_americana(nota): multiplica por 10.americana_a_espanola(nota): divide entre 10.espanola_a_letra(nota): de 9 a 10, A; de 7 a menos de 9, B; de 5 a menos de 7, C; de 3 a menos de 5, D; por debajo de 3, F.letra_a_espanola(letra): A = 9.5, B = 7.5, C = 5.5, D = 3.5, F = 1.5 (el centro de cada tramo), en mayúsculas o minúsculas, yNonesi la letra no existe.
Una quinta función, mostrar_conversiones(nota), enseña las tres versiones de una nota. El programa comprueba que la nota está entre 0 y 10, la muestra y termina con dos conversiones de vuelta. Con nota = 7.5, la salida debe ser:
=== CONVERSOR DE NOTAS === --- Conversiones --- Sistema español: 7.5 Sistema americano: 75.0 Sistema de letras: B De vuelta: 75 → 7.5 · b → 7.5
Cada función de conversión devuelve su resultado con return; solo mostrar_conversiones usa print. Si una conversión hiciera print, americana = espanola_a_americana(7.5) valdría None aunque vieras el 75.0 en la pantalla. Redondea a un decimal con round(…, 1).
Ejercicio 2: generador de informe de notas
Con dos funciones del ejercicio anterior, escribe un programa que reciba las notas de cuatro asignaturas como una lista de parejas (nombre, nota) y genere un informe: cada asignatura en los tres sistemas, la media con su letra y la nota más alta y la más baja con su asignatura.
asignaturas = [("FP1", 8.5), ("Estadística", 7.0), ("Matemáticas", 6.5), ("Bases de datos", 9.0)]
Salida esperada:
=== INFORME DE NOTAS === --- Informe completo --- FP1 → 8.5 | 85.0 | B Estadística → 7.0 | 70.0 | B Matemáticas → 6.5 | 65.0 | C Bases de datos → 9.0 | 90.0 | A Media: 7.75 → B Más alta: 9.0 (Bases de datos) Más baja: 6.5 (Matemáticas)
Crea fila_informe(nombre, nota) que devuelva la línea de una asignatura (no que la imprima) y generar_informe(asignaturas) que la use dentro de un for nombre, nota in asignaturas:. Para la más alta y la más baja, empieza con un valor imposible (−1 y 11) y ve actualizándolo. Ojo: el FP1, con un 8.5, es una B, no una A.
Nivel intermedio
Ejercicio 3: mini banco con funciones
Simula una cuenta bancaria con funciones: consultar_saldo(saldo), depositar(saldo, cantidad), retirar(saldo, cantidad) y mostrar_historial(historial, saldo). La cuenta empieza con 1000.0 € y se hacen, por este orden: un depósito de 500, una retirada de 200, una retirada de 2000 (no hay dinero suficiente) y un depósito de −50 (cantidad no válida). El historial solo apunta los movimientos que salieron bien.
Salida esperada:
=== BANCO SERGIO LEARNS === Saldo actual: 1000.0 € ✓ Depósito realizado: +500 € ✓ Retirada realizada: -200 € Error: saldo insuficiente (tienes 1300.0 €) Error: la cantidad debe ser positiva --- Historial de movimientos --- Saldo inicial: +1000.0 € Depósito: +500 € Retirada: -200 € ────────────────────────────── Saldo actual: 1300.0 €
depositar y retirar reciben el saldo y devuelven el saldo nuevo; si algo falla, devuelven el mismo que recibieron. Ese es el return clave: si lo olvidas, saldo = depositar(saldo, 500) deja el saldo en None. Para saber si hay que apuntar el movimiento, compara el saldo de antes con el de después.
Desafío final
Ejercicio 4: juego de dados con estadísticas
Simula seis lanzamientos de dos dados. Cada lanzamiento se muestra con su suma y un aviso si es doble. Al final, las estadísticas: total de lanzamientos, suma total, media, máximo y mínimo (con el lanzamiento en el que salieron por primera vez), número de dobles con su porcentaje y una distribución con barras de █, de la suma más baja a la más alta, que diga «1 vez» o «2 veces» según toque. Pon random.seed(37) al principio para que tus dados salgan igual que estos:
=== JUEGO DE DADOS === Lanzamiento 1: 🎲 6 + 🎲 5 = 11 Lanzamiento 2: 🎲 1 + 🎲 5 = 6 Lanzamiento 3: 🎲 6 + 🎲 6 = 12 ← ¡Doble! Lanzamiento 4: 🎲 6 + 🎲 5 = 11 Lanzamiento 5: 🎲 1 + 🎲 6 = 7 Lanzamiento 6: 🎲 3 + 🎲 4 = 7 --- Estadísticas --- Total lanzamientos: 6 Suma total: 54 Media por lanzamiento: 9.0 Máximo obtenido: 12 (lanzamiento 3) Mínimo obtenido: 6 (lanzamiento 2) Dobles conseguidos: 1 (16.7 %) Distribución de resultados: 6: █ (1 vez) 7: ██ (2 veces) 11: ██ (2 veces) 12: █ (1 vez)
lanzar_dado() devuelve random.randint(1, 6) y lanzar_dos_dados() devuelve dos valores (return lanzar_dado(), lanzar_dado()), que se recogen con d1, d2 = lanzar_dos_dados(). Guarda las sumas en una lista con append, busca el máximo y el mínimo con funciones que devuelvan valor y posición, y para la distribución recorre range(2, 13) contando con sumas.count(valor). El plural también puede ser una función.
¿Qué falla aquí?
Tres fragmentos que parecen correctos. Para cada uno, predice qué sale antes de leer la explicación.
Fragmento 1: la nota que se ve pero no está
def espanola_a_americana(nota):
print(nota * 10) # enseña 75.0... y devuelve None
americana = espanola_a_americana(7.5)
print("Con 5 puntos extra:", americana + 5)
¿Qué crees que sale?
Sale esto:
75.0
Traceback (most recent call last):
File "print_return.py", line 6, in <module>
print("Con 5 puntos extra:", americana + 5)
~~~~~~~~~~^~~
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'
El 75.0 aparece, así que la conversión «funciona». Pero espanola_a_americana no tiene return, así que devuelve None, y None + 5 es imposible: unsupported operand type(s) for +: 'NoneType' and 'int'. Cuando veas NoneType en un error, lo primero que tienes que pensar es «alguna función no tiene return».
Confiar en la pantalla. Que el número salga no significa que la función lo devuelva: print lo manda a la pantalla y a la variable le llega None.
Arreglado, con return:
def espanola_a_americana(nota):
return nota * 10 # devuelve el número a quien llama
americana = espanola_a_americana(7.5)
print("Con 5 puntos extra:", americana + 5)
Con 5 puntos extra: 80.0
7.5 × 10 = 75.0 y 75.0 + 5 = 80.0. Fíjate en que ahora el 75.0 no sale suelto: nadie lo imprime, solo se usa.
Las funciones de conversión casi nunca deberían imprimir. Si quieres ver el valor mientras programas, imprímelo fuera: print(espanola_a_americana(7.5)).
Fragmento 2: el saldo que nadie encuentra
saldo = 1000.0
def depositar(cantidad):
saldo = saldo + cantidad # ¿la de fuera? Python no lo ve así
return saldo
print(depositar(500))
¿Qué crees que sale? ¿1500.0?
Sale esto:
Traceback (most recent call last):
File "saldo_global.py", line 9, in <module>
print(depositar(500))
~~~~~~~~~^^^^^
File "saldo_global.py", line 5, in depositar
saldo = saldo + cantidad # ¿la de fuera? Python no lo ve así
^^^^^
UnboundLocalError: cannot access local variable 'saldo' where it is not associated with a value
Como depositar asigna saldo = …, Python decide que saldo es una variable local de la función, en todo su cuerpo. Al calcular saldo + cantidad intenta leer esa local, que todavía no tiene valor: UnboundLocalError. La global de 1000.0 existe, pero la función ya no la mira.
Pensar que una función puede leer una variable de fuera y cambiarla a la vez sin decir nada. Para leerla, sí; para asignarla, Python la convierte en local.
El arreglo no es global saldo, sino hacer que el dato entre y salga a la vista:
def depositar(saldo, cantidad):
return saldo + cantidad # entra por parámetro, sale por return
saldo = 1000.0
saldo = depositar(saldo, 500) # y se guarda fuera, a la vista
print(saldo)
1500.0
Así es exactamente como funciona el mini banco del ejercicio 3: el saldo entra por parámetro y sale por return.
Fragmento 3: el return que se rindió
El de la anécdota. La partida tiene un doble en la tercera tirada:
def hay_doble(tiradas):
"""¿Ha salido algún doble en la partida?"""
for d1, d2 in tiradas:
if d1 == d2:
return True
else:
return False # ← ojo con este return
tiradas = [(6, 5), (1, 5), (6, 6)]
print(hay_doble(tiradas))
¿Qué crees que sale?
Sale esto:
False
En la primera vuelta, la tirada es (6, 5): no es doble, así que entra en el else y hace return False. Y return no termina la vuelta, termina la función. Las tiradas (1, 5) y (6, 6) no se miran nunca. El dibujo compara las dos versiones con la misma partida:
Escribir el «no lo he encontrado» dentro del bucle. Para decir que no hay ningún doble tienes que haber mirado todas las tiradas, y eso solo se sabe cuando el bucle termina.
Arreglado: el return False sale del bucle y del else:
def hay_doble(tiradas):
"""¿Ha salido algún doble en la partida?"""
for d1, d2 in tiradas:
if d1 == d2:
return True # en cuanto aparece uno, ya sabemos la respuesta
return False # solo después de mirar TODAS las tiradas
tiradas = [(6, 5), (1, 5), (6, 6)]
print(hay_doble(tiradas))
True
En las búsquedas, el patrón es siempre el mismo: return en cuanto encuentras lo que buscas, dentro del bucle; y el return de «no está», después del bucle. Es la misma forma que la búsqueda de las bandejas de la teoría.
Soluciones comentadas
Solución del ejercicio 1
# Nivel básico · Ejercicio 1: conversor de notas entre sistemas
def espanola_a_americana(nota):
"""0-10 → 0-100."""
return round(nota * 10, 1)
def americana_a_espanola(nota):
"""0-100 → 0-10."""
return round(nota / 10, 1)
def espanola_a_letra(nota):
"""9-10 A · 7-8,9 B · 5-6,9 C · 3-4,9 D · menos de 3 F."""
if nota >= 9:
return "A"
elif nota >= 7:
return "B"
elif nota >= 5:
return "C"
elif nota >= 3:
return "D"
return "F"
def letra_a_espanola(letra):
"""Cada letra vale el centro de su tramo; None si la letra no existe."""
letra = letra.upper()
if letra == "A":
return 9.5
if letra == "B":
return 7.5
if letra == "C":
return 5.5
if letra == "D":
return 3.5
if letra == "F":
return 1.5
return None
def mostrar_conversiones(nota):
"""La única que imprime: recibe la nota y enseña las tres versiones."""
print("--- Conversiones ---")
print("Sistema español:", nota)
print("Sistema americano:", espanola_a_americana(nota))
print("Sistema de letras:", espanola_a_letra(nota))
print("=== CONVERSOR DE NOTAS ===")
nota = 7.5 # la nota que antes se pedía con input()
if nota < 0 or nota > 10:
print("Error: la nota debe estar entre 0 y 10")
else:
mostrar_conversiones(nota)
print("De vuelta: 75 →", americana_a_espanola(75), "· b →", letra_a_espanola("b"))
Salida:
=== CONVERSOR DE NOTAS === --- Conversiones --- Sistema español: 7.5 Sistema americano: 75.0 Sistema de letras: B De vuelta: 75 → 7.5 · b → 7.5
Por qué está hecho así:
- Cuatro funciones que devuelven y una que imprime. Las conversiones se pueden reutilizar en el ejercicio 2 justo porque no imprimen nada.
- Los tramos con
>=en orden descendente.espanola_a_letra(7.5):7.5 >= 9es falso,7.5 >= 7es verdadero →"B". Como loselifse miran de arriba abajo, cada uno solo necesita el límite inferior. Si los escribieras de abajo arriba (nota >= 3primero), un 9.5 saldría como D. letra.upper()antes de comparar, para que"b"y"B"valgan lo mismo; si la letra no existe, se llega alreturn Nonedel final.- Nombres sin ñ.
espanolay noespañola: Python 3 acepta la ñ en los nombres, pero es fácil que un compañero no pueda escribirla en su teclado o que alguna herramienta se queje. Es una costumbre sana. - La comprobación del rango, fuera de las conversiones. Las conversiones no deciden qué hacer con una nota imposible; lo decide el programa principal.
Cuentas: 7.5 × 10 = 75.0; 75 / 10 = 7.5; "b" → "B" → 7.5.
Solución del ejercicio 2
# Nivel básico · Ejercicio 2: informe de notas (reutiliza dos funciones del ejercicio 1)
def espanola_a_americana(nota):
return round(nota * 10, 1)
def espanola_a_letra(nota):
if nota >= 9:
return "A"
elif nota >= 7:
return "B"
elif nota >= 5:
return "C"
elif nota >= 3:
return "D"
return "F"
def fila_informe(nombre, nota):
"""DEVUELVE la línea de una asignatura (no la imprime: así se puede reutilizar)."""
return f"{nombre} → {nota} | {espanola_a_americana(nota)} | {espanola_a_letra(nota)}"
def generar_informe(asignaturas):
print("--- Informe completo ---")
suma = 0
cuantas = 0
nota_max, nombre_max = -1, "" # empezamos por debajo de cualquier nota posible...
nota_min, nombre_min = 11, "" # ...y por encima, para que la primera ya gane
for nombre, nota in asignaturas:
print(fila_informe(nombre, nota))
suma += nota
cuantas += 1
if nota > nota_max: # > y no >=: con un empate se queda la primera
nota_max, nombre_max = nota, nombre
if nota < nota_min:
nota_min, nombre_min = nota, nombre
media = round(suma / cuantas, 2)
print("Media:", media, "→", espanola_a_letra(media))
print(f"Más alta: {nota_max} ({nombre_max})")
print(f"Más baja: {nota_min} ({nombre_min})")
print("=== INFORME DE NOTAS ===")
generar_informe([("FP1", 8.5), ("Estadística", 7.0), ("Matemáticas", 6.5), ("Bases de datos", 9.0)])
Salida:
=== INFORME DE NOTAS === --- Informe completo --- FP1 → 8.5 | 85.0 | B Estadística → 7.0 | 70.0 | B Matemáticas → 6.5 | 65.0 | C Bases de datos → 9.0 | 90.0 | A Media: 7.75 → B Más alta: 9.0 (Bases de datos) Más baja: 6.5 (Matemáticas)
Por qué está hecho así:
fila_informedevuelve el texto.generar_informelo imprime, pero la misma función serviría para escribir el informe en un archivo.- Las dos funciones del ejercicio 1, copiadas. Aquí se repiten porque cada ejercicio es un archivo independiente. Copiar funciones entre archivos es justo lo que resuelve el siguiente tema, los módulos en Python: escribirías
from conversor_notas import espanola_a_letray listo. - Valores imposibles para empezar.
nota_max = -1garantiza que la primera nota (cualquiera entre 0 y 10) ya sea mayor;nota_min = 11, que sea menor. >y no>=. Con un empate, se queda la primera asignatura que alcanzó esa nota.for nombre, nota in asignaturasdesempaqueta cada pareja en dos variables, igual queeuros, cents = …en la teoría.
Traza del máximo y el mínimo:
| Vuelta | Asignatura | nota | suma | nota_max (nombre_max) | nota_min (nombre_min) |
|---|---|---|---|---|---|
| inicio | — | — | 0 | −1 | 11 |
| 1 | FP1 | 8.5 | 8.5 | 8.5 (FP1) | 8.5 (FP1) |
| 2 | Estadística | 7.0 | 15.5 | 8.5 (FP1) | 7.0 (Estadística) |
| 3 | Matemáticas | 6.5 | 22.0 | 8.5 (FP1) | 6.5 (Matemáticas) |
| 4 | Bases de datos | 9.0 | 31.0 | 9.0 (Bases de datos) | 6.5 (Matemáticas) |
Media: 31.0 / 4 = 7.75, que está entre 7 y 9 → B.
Solución del ejercicio 3
# Nivel intermedio · Ejercicio 3: mini banco con funciones
def consultar_saldo(saldo):
print(f"Saldo actual: {saldo} €")
def depositar(saldo, cantidad):
"""Devuelve el saldo NUEVO. Si la cantidad no vale, devuelve el mismo saldo."""
if cantidad <= 0:
print("Error: la cantidad debe ser positiva")
return saldo
print(f"✓ Depósito realizado: +{cantidad} €")
return round(saldo + cantidad, 2) # el return clave: sin él, el saldo nunca cambia
def retirar(saldo, cantidad):
"""Devuelve el saldo nuevo, o el mismo si no hay dinero suficiente."""
if cantidad <= 0:
print("Error: la cantidad debe ser positiva")
return saldo
if cantidad > saldo:
print(f"Error: saldo insuficiente (tienes {saldo} €)")
return saldo
print(f"✓ Retirada realizada: -{cantidad} €")
return round(saldo - cantidad, 2)
def anotar(historial, texto, saldo_antes, saldo_despues):
"""Apunta el movimiento solo si el saldo ha cambiado (si hubo error, no cambia)."""
if saldo_despues != saldo_antes:
historial.append(texto)
def mostrar_historial(historial, saldo):
print("--- Historial de movimientos ---")
for movimiento in historial:
print(movimiento)
print("─" * 30)
consultar_saldo(saldo)
print("=== BANCO SERGIO LEARNS ===")
saldo = 1000.0
historial = [f"Saldo inicial: +{saldo} €"]
consultar_saldo(saldo)
antes = saldo
saldo = depositar(saldo, 500) # se guarda lo que DEVUELVE la función
anotar(historial, "Depósito: +500 €", antes, saldo)
antes = saldo
saldo = retirar(saldo, 200)
anotar(historial, "Retirada: -200 €", antes, saldo)
antes = saldo
saldo = retirar(saldo, 2000) # no hay tanto: el saldo no cambia
anotar(historial, "Retirada: -2000 €", antes, saldo)
antes = saldo
saldo = depositar(saldo, -50) # cantidad no válida
anotar(historial, "Depósito: -50 €", antes, saldo)
mostrar_historial(historial, saldo)
Salida:
=== BANCO SERGIO LEARNS === Saldo actual: 1000.0 € ✓ Depósito realizado: +500 € ✓ Retirada realizada: -200 € Error: saldo insuficiente (tienes 1300.0 €) Error: la cantidad debe ser positiva --- Historial de movimientos --- Saldo inicial: +1000.0 € Depósito: +500 € Retirada: -200 € ────────────────────────────── Saldo actual: 1300.0 €
Por qué está hecho así:
- El saldo nunca es global para las funciones. Entra por parámetro y sale por
return, y el programa principal lo reasigna:saldo = depositar(saldo, 500). Es el arreglo del fragmento 2 aplicado a un programa completo. - Si algo falla, se devuelve el mismo saldo. Así quien llama no tiene que distinguir casos: siempre recibe un saldo válido.
anotarcompara antes y después. Si el saldo no cambió, la operación falló y no se apunta. Con la retirada de 2000 y el depósito de −50, el historial se queda en tres líneas.- El historial es una lista que
anotarmodifica conappend. Las listas se pueden cambiar «por dentro» desde una función sin devolverlas; los números no. Lo verás a fondo en el tema de mutabilidad; por ahora, fíjate en que el saldo (un número) necesita elreturny el historial (una lista) no. round(…, 2)en las operaciones, para que un saldo como 0.1 + 0.2 no acabe con colas de decimales.
El dibujo sigue el saldo de función en función. Cada píldora violeta es lo que sale de un return y entra en la siguiente llamada:
Cuentas: 1000.0 + 500 = 1500.0; 1500.0 − 200 = 1300.0; 2000 > 1300.0, así que la retirada falla; −50 ≤ 0, así que el depósito falla. Saldo final: 1300.0 €.
Solución del ejercicio 4
# Desafío · Ejercicio 4: juego de dados con estadísticas
import random
random.seed(37) # semilla fija para que la salida sea siempre la misma (quítala para jugar)
def lanzar_dado():
return random.randint(1, 6) # return: devuelve el número, no lo imprime
def lanzar_dos_dados():
return lanzar_dado(), lanzar_dado() # dos valores a la vez (una tupla)
def es_doble(dado1, dado2):
return dado1 == dado2
def maximo_y_posicion(sumas):
"""Devuelve el valor más alto y en qué lanzamiento salió (el primero si se repite)."""
mejor, posicion, i = 0, 0, 0
for s in sumas:
i += 1
if s > mejor:
mejor, posicion = s, i
return mejor, posicion
def minimo_y_posicion(sumas):
peor, posicion, i = 13, 0, 0 # 13: más que la suma máxima posible (6 + 6)
for s in sumas:
i += 1
if s < peor:
peor, posicion = s, i
return peor, posicion
def veces(n):
"""«1 vez» o «2 veces»: el plural también es una decisión."""
if n == 1:
return "1 vez"
return f"{n} veces"
def mostrar_distribucion(sumas):
print("Distribución de resultados:")
for valor in range(2, 13): # todas las sumas posibles, ya en orden
n = sumas.count(valor) # cuántas veces salió ese valor
if n > 0:
print(f" {valor:2d}: {'█' * n} ({veces(n)})")
def jugar(lanzamientos):
print("=== JUEGO DE DADOS ===")
sumas = []
dobles = 0
for i in range(1, lanzamientos + 1):
d1, d2 = lanzar_dos_dados() # desempaquetamos la tupla
suma = d1 + d2
aviso = ""
if es_doble(d1, d2):
aviso = " ← ¡Doble!"
dobles += 1
print(f"Lanzamiento {i}: 🎲 {d1} + 🎲 {d2} = {suma}{aviso}")
sumas.append(suma)
maximo, lanz_max = maximo_y_posicion(sumas)
minimo, lanz_min = minimo_y_posicion(sumas)
print("--- Estadísticas ---")
print("Total lanzamientos:", lanzamientos)
print("Suma total:", sum(sumas))
print("Media por lanzamiento:", round(sum(sumas) / lanzamientos, 2))
print(f"Máximo obtenido: {maximo} (lanzamiento {lanz_max})")
print(f"Mínimo obtenido: {minimo} (lanzamiento {lanz_min})")
print(f"Dobles conseguidos: {dobles} ({round(dobles / lanzamientos * 100, 1)} %)")
mostrar_distribucion(sumas)
jugar(6)
Salida:
=== JUEGO DE DADOS === Lanzamiento 1: 🎲 6 + 🎲 5 = 11 Lanzamiento 2: 🎲 1 + 🎲 5 = 6 Lanzamiento 3: 🎲 6 + 🎲 6 = 12 ← ¡Doble! Lanzamiento 4: 🎲 6 + 🎲 5 = 11 Lanzamiento 5: 🎲 1 + 🎲 6 = 7 Lanzamiento 6: 🎲 3 + 🎲 4 = 7 --- Estadísticas --- Total lanzamientos: 6 Suma total: 54 Media por lanzamiento: 9.0 Máximo obtenido: 12 (lanzamiento 3) Mínimo obtenido: 6 (lanzamiento 2) Dobles conseguidos: 1 (16.7 %) Distribución de resultados: 6: █ (1 vez) 7: ██ (2 veces) 11: ██ (2 veces) 12: █ (1 vez)
Por qué está hecho así:
lanzar_dadodevuelve, no imprime. Si imprimiera,d1 + d2seríaNone + None.lanzar_dos_dadosdevuelve una tupla que se desempaqueta end1, d2. Una función, dos valores.maximo_y_posicionyminimo_y_posiciondevuelven dos cosas: el valor y el lanzamiento. Con>(y<) se quedan con la primera vez: el 11 sale en los lanzamientos 1 y 4, pero el máximo es el 12 del 3; y el mínimo, el 6 del 2.range(2, 13)para la distribución. Recorre todas las sumas posibles ya en orden, así que no hace falta ordenar nada;sumas.count(valor)dice cuántas veces salió y elif n > 0se salta las que no salieron.veces(n), una función diminuta que evita el «(2 vez)» de la versión antigua de este ejercicio.random.seed(37)para que la salida sea siempre la misma. Bórrala para jugar de verdad.
Traza de las estadísticas:
| Lanzamiento | Dados | Suma | ¿Doble? | Máximo hasta aquí | Mínimo hasta aquí |
|---|---|---|---|---|---|
| 1 | 6 + 5 | 11 | no | 11 (1) | 11 (1) |
| 2 | 1 + 5 | 6 | no | 11 (1) | 6 (2) |
| 3 | 6 + 6 | 12 | sí | 12 (3) | 6 (2) |
| 4 | 6 + 5 | 11 | no | 12 (3) | 6 (2) |
| 5 | 1 + 6 | 7 | no | 12 (3) | 6 (2) |
| 6 | 3 + 4 | 7 | no | 12 (3) | 6 (2) |
Suma: 11 + 6 + 12 + 11 + 7 + 7 = 54; media 54 / 6 = 9.0; dobles 1 de 6 = 16.7 %.
Cuestionario tipo examen
Ahora ponte a prueba como en el examen. Los cuestionarios de FP1 son así: 15 preguntas en 20 minutos (16 en 30 en el de Secuencias), casi todas de respuesta única (cada fallo resta un tercio de lo que vale la pregunta) y muchas de «¿qué imprime este código?», con algún hueco, relacionar o verdadero o falso. Aquí tienes 13 preguntas originales sobre funciones. Contesta sin ejecutar nada, pulsa Comprobar y lee la explicación de cada una. Estas preguntas son también el banco del simulacro de teoría de FP1, que las mezclará con las de los demás temas.
¿Qué imprime este programa? Escribe la salida exacta.
def doble_mas(x, y=1):
return 2 * x + y
print(doble_mas(4) + doble_mas(3, 5))Solución
Respuesta: 20
doble_mas(4) usa el valor por defecto y = 1: 2 × 4 + 1 = 9. doble_mas(3, 5) da 2 × 3 + 5 = 11. Total: 9 + 11 = 20.
¿Qué imprime este código?
def saludar(nombre):
print("Hola", nombre)
r = saludar("Luis")
print(r)Hola Luisy despuésNoneHola Luisdos veces- Solo
Hola Luis - Un error
Solución
Respuesta: Hola Luis y después None
print muestra y return devuelve. saludar muestra el saludo pero no devuelve nada, así que r vale None. Confundirlos es el error más típico del tema.
En def area(base, altura): … area(4, 3), relaciona cada término:
base y altura | |
4 y 3 | |
area | |
area(4, 3) |
Definiciones:
- Llamada a la función
- Parámetros formales
- Argumentos (parámetros reales)
- Valor por defecto
- Nombre de la función
Solución
baseyaltura→ Parámetros formales4y3→ Argumentos (parámetros reales)area→ Nombre de la funciónarea(4, 3)→ Llamada a la función
Los parámetros son los nombres que aparecen en la definición; los argumentos, los valores concretos que pasas al llamarla. En la llamada, cada argumento se asigna a su parámetro por orden.
¿Cómo se llama pasar los argumentos así: area(altura=3, base=4)?
- Paso por nombre (argumentos con nombre o keyword arguments)
- Paso por posición
- Valores por defecto
- Recursividad
Solución
Respuesta: Paso por nombre (argumentos con nombre o keyword arguments)
Con nombre=valor asocias cada argumento a su parámetro explícitamente. Es útil cuando una función tiene muchos parámetros o algunos con valor por defecto.
¿Qué imprime este programa? Escribe la salida exacta.
def f(n):
if n > 10:
return "grande"
return "pequeño"
print("fin")
print(f(15))Solución
Respuesta: grande
return termina la función en ese mismo instante. Con 15 se cumple el if y devuelve grande. El print("fin") está después de un return, así que no se ejecuta nunca.
¿Qué imprime este código?
x = 5
def cambiar():
x = 99
return x
cambiar()
print(x)599- Un error, porque
xya existe None
Solución
Respuesta: 5
Cada llamada tiene su propio ámbito: x = 99 crea una x local que desaparece al acabar la función. La x global sigue en 5. Para sacar un valor de una función, devuélvelo con return.
Sobre las funciones:
- Una función puede devolver varios valores separados por comas:
return a, b. - Una función se ejecuta en cuanto Python lee su
def. - Una función puede llamar a otra función.
- Los parámetros con valor por defecto pueden ir antes de los que no lo tienen:
def f(a=1, b):.
Solución
- V — Una función puede devolver varios valores separados por comas:
return a, b. - F — Una función se ejecuta en cuanto Python lee su
def. - V — Una función puede llamar a otra función.
- F — Los parámetros con valor por defecto pueden ir antes de los que no lo tienen:
def f(a=1, b):.
return a, b devuelve una tupla. El def solo define: el cuerpo se ejecuta al llamarla. Y los parámetros con valor por defecto van siempre al final; def f(a=1, b): es un error de sintaxis.
¿Qué imprime este programa? Escribe la salida exacta.
def minmax(a, b):
if a < b:
return a, b
return b, a
menor, mayor = minmax(8, 3)
print(mayor - menor)Solución
Respuesta: 5
Con 8 y 3, a < b es falso y devuelve (3, 8). Al desempaquetar, menor = 3 y mayor = 8: 8 − 3 = 5.
¿Qué ventaja tiene dividir un programa en funciones?
- Se puede reutilizar el código y probar cada parte por separado
- El programa se ejecuta siempre más rápido
- Ya no hacen falta variables
- Python lo exige: sin funciones no se puede ejecutar nada
Solución
Respuesta: Se puede reutilizar el código y probar cada parte por separado
Es la idea de la programación estructurada: dividir un problema grande en piezas pequeñas con un nombre claro. Si una pieza falla, sabes dónde mirar.
Completa la función para que devuelva la media de dos números:
____ media(a, b): y dentro ____ (a + b) / 2
Palabras:
- function
- input
- return
- def
Solución
def media(a, b): y dentro return (a + b) / 2
Las funciones se definen con def. Si usaras print en lugar de return, la media se vería en pantalla, pero m = media(4, 6) guardaría None.
¿Qué imprime este código?
def g(x):
return x * 2
def h(x):
return g(x) + 1
print(h(g(3)))1371214
Solución
Respuesta: 13
Se evalúa de dentro hacia fuera: primero g(3) = 6, y luego h(6), que calcula g(6) + 1 = 12 + 1 = 13.
¿Qué devuelve max(3, 9, 4)?
93(3, 9, 4)- Un error:
maxsolo admite listas
Solución
Respuesta: 9
Python trae funciones predefinidas (built-in) como print, len, max, min, abs o round. Se usan igual que las que defines tú.
¿Qué imprime este código?
def contar(n):
c = 0
while n > 0:
n = n // 10
c += 1
return c
print(contar(2026))4202630
Solución
Respuesta: 4
Cada // 10 quita la última cifra. El bucle da una vuelta por cifra, así que la función cuenta cuántas tiene el número: 4.
Míralo paso a paso en Python Tutor
Python Tutor es la mejor forma de ver el fallo del fragmento 3 en directo. Elige Python 3, pega esto y avanza con Next:
def hay_doble_mal(tiradas):
for d1, d2 in tiradas:
if d1 == d2:
return True
else:
return False
def hay_doble_bien(tiradas):
for d1, d2 in tiradas:
if d1 == d2:
return True
return False
tiradas = [(6, 5), (1, 5), (6, 6)]
print(hay_doble_mal(tiradas))
print(hay_doble_bien(tiradas))
Salida esperada:
False True
Qué observar paso a paso:
- Al entrar en
hay_doble_mal, la caja de la función recibetiradascomo una flecha a la misma lista del Global frame: no se copia. - En la primera vuelta,
d1 = 6yd2 = 5. La flecha salta alelsey, en el paso siguiente, aparece Return value: False. La caja desaparece con el bucle a medias: las otras dos tiradas no se han mirado. - En
hay_doble_bien, las dos primeras vueltas no hacen nada (la condición es falsa y no hayelse) y la flecha vuelve alfor. - En la tercera vuelta,
d1 = 6yd2 = 6: Return value: True. La líneareturn Falsedel final no llega a ejecutarse nunca en esta partida.
Chuletario: funciones en Python
Lo esencial en 30 segundos: una función se define con def nombre(parametros): y se ejecuta al llamarla con nombre(argumentos). return devuelve un valor y termina la función; sin return, devuelve None. print enseña, return entrega. Los parámetros y las variables de dentro son locales: nacen con la llamada y mueren al terminar. Entrada por parámetros, salida por return.
Definir y llamar:
| Quiero… | Código | Resultado |
|---|---|---|
| Definir una función | def cobrar(total, pago): | no ejecuta nada todavía |
| Documentarla | """Devuelve el cambio.""" en la primera línea | se lee con cobrar.__doc__ |
| Llamarla | cobrar(3.70, 5.00) | 1.3 |
| Guardar lo que devuelve | cambio = cobrar(3.70, 5.00) | cambio vale 1.3 |
| Pasar argumentos por nombre | cobrar(pago=5.00, total=3.70) | 1.3 |
| Un parámetro opcional | def cobrar(total, pago, propina=0): | cobrar(3.70, 5.00) usa propina = 0 |
| Anotar tipos | def f(precio: float) -> float: | informativo, no se comprueba |
Devolver:
| Quiero… | Código | Resultado |
|---|---|---|
| Devolver un valor | return round(pago - total, 2) | la llamada vale ese número |
| Devolver dos valores | return euros, cents | una tupla: e, c = f(...) |
| Salir sin valor | return | devuelve None |
| Avisar de que no hay resultado | return None y if r is None: | quien llama decide el mensaje |
| Salir de dos bucles | return dentro del bucle de dentro | sale de la función entera |
| Buscar y decir «no está» | return dentro; return None después del for | la búsqueda de las bandejas |
Plantillas de los ejercicios:
# Conversión: recibe, calcula y DEVUELVE (ejercicio 1)
def espanola_a_americana(nota):
return round(nota * 10, 1)
# El dato entra por parámetro y sale por return (ejercicio 3)
saldo = depositar(saldo, 500)
# Máximo con su posición, empezando por un valor imposible (ejercicios 2 y 4)
def maximo_y_posicion(sumas):
mejor, posicion, i = 0, 0, 0
for s in sumas:
i += 1
if s > mejor:
mejor, posicion = s, i
return mejor, posicion
# Búsqueda: return dentro si lo encuentras, después del bucle si no (fragmento 3)
def hay_doble(tiradas):
for d1, d2 in tiradas:
if d1 == d2:
return True
return False
Síntoma y causa probable:
| Síntoma | Causa probable |
|---|---|
TypeError: … 'NoneType' … | una función sin return (o con print en su lugar) |
La variable vale None y en la pantalla salía el número | print donde hacía falta return |
NameError: name 'x' is not defined fuera de la función | x era local: devuélvela con return |
UnboundLocalError: cannot access local variable | asignas dentro una variable de fuera: recíbela y devuélvela |
TypeError: f() missing 1 required positional argument | falta un argumento en la llamada |
SyntaxError: parameter without a default follows… | un parámetro opcional delante de uno obligatorio |
| Resultado con el signo cambiado o absurdo | argumentos en el orden equivocado: usa nombres |
| Una búsqueda dice «no» aunque el dato está | return False dentro del bucle (en un else) |
| La función «no hace nada» | la llamaste sin paréntesis o no la llamaste |
Antes de entregar, pregúntate:
- ¿Cada función que calcula tiene
return, y solo las de presentación hacenprint? - ¿Guardo lo que devuelven las funciones (
saldo = depositar(saldo, 500)) en lugar de llamarlas sueltas? - ¿Mis búsquedas ponen el «no está» después del bucle y no dentro?
- ¿Hay alguna variable global que una función cambie? Si la hay, ¿puedo pasarla y devolverla?
Si has llegado hasta aquí con los cuatro ejercicios resueltos, ya tienes funciones de sobra para el siguiente paso: guardarlas en archivos propios y usarlas desde cualquier programa con los módulos en Python.
Sigue aprendiendoTodos los artículos de Python¿Te ha servido este artículo? Así puedes apoyar SergioLearns →




Qué es un sistema operativo — teoría con tu portátil en una tarde de estudio, una llamada a write() y la CPU que nunca descansa (y los tres programas que no se ejecutaban a la vez)