Funciones en Python — práctica con un conversor de temperaturas, una calculadora modular y piedra, papel o tijera (y el menú que dejó de pedir datos)
La primera versión de mi calculadora de FP1 tenía un menú precioso y un problema: para comprobar si la división entre cero estaba bien, tenía que ejecutar el programa, elegir la opción 4, teclear 7, teclear 0… y repetirlo cada vez que cambiaba una línea. A la décima vez me equivoqué de tecla, el programa se colgó en otro sitio y ya no sabía qué estaba probando. En la revisión, el profesor señaló la función dividir y me preguntó: «¿y por qué esta función lee del teclado?». No tenía respuesta. La lección que me llevé es la de toda esta práctica: las funciones que calculan no deberían ni pedir datos ni imprimirlos. Reciben, calculan y devuelven. Lo demás va aparte.
En la teoría de funciones en Python viste def, los parámetros, return, la pila de llamadas y el ámbito. Aquí lo usamos en tres programas completos de nivel medio, los mismos de siempre pero rehechos: un conversor de temperaturas donde unas funciones se apoyan en otras, una calculadora modular que separa el cálculo de la presentación y un piedra, papel o tijera que limpia lo que «teclea» el jugador. Ninguno usa input(): los datos van escritos al final de cada programa, como si fueran lo que el usuario tecleó (con mayúsculas, espacios y alguna errata), para que puedas ejecutarlos y obtener exactamente la misma salida que aquí.
Tabla de Contenidos
Antes de empezar
Conviene tener claro:
- Cómo se define y se llama una función, y que
defno ejecuta el cuerpo. - La diferencia entre
print(enseñar) yreturn(devolver), y que sinreturnse devuelveNone. - Que una función puede llamar a otras y que cada llamada tiene sus propias variables locales.
- Los condicionales (
if,elif,else) y los buclesfor.
Si alguno te suena dudoso, repasa la teoría antes de seguir.
Crea una carpeta practica_funciones con un archivo por programa (conversor.py, calculadora.py, ppt.py) y ábrelos en VS Code o en Thonny. Ejecuta cada uno antes de leer su explicación y compara con la salida del artículo.
Programa 1: conversor de temperaturas modular
Un conversor entre Celsius, Fahrenheit y Kelvin con seis opciones: de °C a °F (1), de °C a K (2), de °F a °C (3), de K a °C (4), una conversión completa desde °C (5) y de °F a K (6). Lo que lo complica: hay que rechazar opciones que no existen y temperaturas imposibles (por debajo del cero absoluto), y queremos que ninguna fórmula esté escrita dos veces.
La idea
Repartimos el trabajo en tres tipos de funciones:
- De cálculo: una por fórmula. Reciben un número y devuelven otro. No imprimen.
- De comprobación: responden una pregunta (¿de qué escala parte esta opción?, ¿esta temperatura es posible?) y devuelven la respuesta.
- De presentación:
convertir(opcion, valor)decide qué hacer y devuelve el texto que se va a ver. Solo el programa principal haceprint.
Y una idea clave: para pasar de °F a K no escribimos una fórmula nueva. Ya sabemos pasar de °F a °C y de °C a K: encadenamos las dos funciones. A eso se le llama composición.
Antes de programar, piensa en qué cosas haces más de una vez. Aquí, convertir °C a °F aparece en la opción 1 y en la conversión completa: eso es una función.
El código
# Programa 1 · Conversor de temperaturas modular
# Sin input(): las pruebas van al final, cada una con su opción y su temperatura.
# --- Funciones de cálculo: reciben un número y DEVUELVEN otro (no imprimen nada) ---
def celsius_a_fahrenheit(celsius):
return round(celsius * 9 / 5 + 32, 2)
def celsius_a_kelvin(celsius):
return round(celsius + 273.15, 2)
def fahrenheit_a_celsius(fahrenheit):
return round((fahrenheit - 32) * 5 / 9, 2)
def kelvin_a_celsius(kelvin):
return round(kelvin - 273.15, 2)
def fahrenheit_a_kelvin(fahrenheit):
# composición: no copiamos fórmulas, encadenamos dos funciones que ya funcionan
return celsius_a_kelvin(fahrenheit_a_celsius(fahrenheit))
# --- Funciones de comprobación: responden una pregunta y devuelven la respuesta ---
def escala_de_origen(opcion):
"""Opciones 1, 2 y 5 parten de °C; la 3 y la 6, de °F; la 4, de K."""
if opcion == 3 or opcion == 6:
return "°F"
if opcion == 4:
return "K"
return "°C"
def es_temperatura_valida(valor, escala):
"""Nada puede estar por debajo del cero absoluto (−273,15 °C = −459,67 °F = 0 K)."""
if escala == "°C":
return valor >= -273.15
if escala == "°F":
return valor >= -459.67
return valor >= 0
# --- Funciones de presentación: preparan el texto que se verá ---
def conversion_completa(celsius):
"""Las tres escalas de golpe y una comprobación de ida y vuelta (°C → °F → °C)."""
fahrenheit = celsius_a_fahrenheit(celsius)
vuelta = fahrenheit_a_celsius(fahrenheit) # si todo está bien, vuelve al valor de partida
return f"{celsius} °C = {fahrenheit} °F = {celsius_a_kelvin(celsius)} K · ida y vuelta: {vuelta} °C"
def convertir(opcion, valor):
"""Devuelve el texto de la conversión elegida (1-6) o un aviso si algo no cuadra."""
if opcion < 1 or opcion > 6:
return f"Opción {opcion} no válida: elige de 1 a 6"
escala = escala_de_origen(opcion)
if not es_temperatura_valida(valor, escala):
return f"{valor} {escala} está por debajo del cero absoluto"
if opcion == 1:
return f"{valor} °C = {celsius_a_fahrenheit(valor)} °F"
if opcion == 2:
return f"{valor} °C = {celsius_a_kelvin(valor)} K"
if opcion == 3:
return f"{valor} °F = {fahrenheit_a_celsius(valor)} °C"
if opcion == 4:
return f"{valor} K = {kelvin_a_celsius(valor)} °C"
if opcion == 6:
return f"{valor} °F = {fahrenheit_a_kelvin(valor)} K"
return conversion_completa(valor) # solo queda la opción 5
# --- Programa principal: cada línea es lo que antes tecleaba el usuario ---
print("=== CONVERSOR DE TEMPERATURAS ===")
print(convertir(1, 100))
print(convertir(2, -40))
print(convertir(3, 98.6))
print(convertir(4, 0))
print(convertir(5, 37))
print(convertir(6, 212))
print(convertir(1, -300))
print(convertir(9, 20))
Salida
=== CONVERSOR DE TEMPERATURAS === 100 °C = 212.0 °F -40 °C = 233.15 K 98.6 °F = 37.0 °C 0 K = -273.15 °C 37 °C = 98.6 °F = 310.15 K · ida y vuelta: 37.0 °C 212 °F = 373.15 K -300 °C está por debajo del cero absoluto Opción 9 no válida: elige de 1 a 6
Comprobamos a mano: 100 × 9 / 5 + 32 = 180 + 32 = 212; −40 + 273.15 = 233.15; (98.6 − 32) × 5 / 9 = 66.6 × 5 / 9 = 37.0; 0 − 273.15 = −273.15. La opción 5 hace las tres de golpe con 37 °C: 37 × 9 / 5 + 32 = 98.6 °F y 37 + 273.15 = 310.15 K, y la vuelta de 98.6 °F a °C da otra vez 37.0, señal de que las dos fórmulas son coherentes entre sí. La opción 6 pasa 212 °F a 100.0 °C y de ahí a 373.15 K. Las dos últimas líneas son los casos incómodos: −300 °C no existe y la opción 9 tampoco.
La función que decide: convertir
Es la más larga, así que la miramos sola:
def convertir(opcion, valor):
"""Devuelve el texto de la conversión elegida (1-6) o un aviso si algo no cuadra."""
if opcion < 1 or opcion > 6:
return f"Opción {opcion} no válida: elige de 1 a 6"
escala = escala_de_origen(opcion)
if not es_temperatura_valida(valor, escala):
return f"{valor} {escala} está por debajo del cero absoluto"
if opcion == 1:
return f"{valor} °C = {celsius_a_fahrenheit(valor)} °F"
if opcion == 2:
return f"{valor} °C = {celsius_a_kelvin(valor)} K"
if opcion == 3:
return f"{valor} °F = {fahrenheit_a_celsius(valor)} °C"
if opcion == 4:
return f"{valor} K = {kelvin_a_celsius(valor)} °C"
if opcion == 6:
return f"{valor} °F = {fahrenheit_a_kelvin(valor)} K"
return conversion_completa(valor) # solo queda la opción 5
Va en este orden, y el orden importa:
- Primero, la opción. Si no está entre 1 y 6, devuelve el aviso y termina (el
returncorta la función: no se mira nada más). - Después, la temperatura. Pregunta a
escala_de_origende qué escala parte la opción y aes_temperatura_validasi el valor es posible. Si no, otro aviso y fuera. - Por último, la conversión. Cada
iftiene sureturn, así que no hace faltaelif: en cuanto uno se cumple, la función termina. Si no se ha cumplido ninguno, solo puede ser la opción 5.
Traza de convertir(1, -300):
| Paso | Llamada | Devuelve | Consecuencia |
|---|---|---|---|
| 1 | opcion < 1 or opcion > 6 → 1 < 1 or 1 > 6 | False | sigue |
| 2 | escala_de_origen(1) | "°C" | escala = "°C" |
| 3 | es_temperatura_valida(-300, "°C") → -300 >= -273.15 | False | not False es True |
| 4 | return f"{valor} {escala} está por debajo…" | el aviso | la función termina |
Y así se reparten el trabajo las funciones cuando eliges la opción 5 con 37 °C. Cada caja es una llamada y el valor coral, lo que devuelve:
Preguntas que te puedes hacer
¿Por qué las funciones de cálculo redondean con round(…, 2)? Porque los decimales en binario no son exactos (en Python, 0.1 + 0.2 da 0.30000000000000004) y una conversión puede arrastrar colas así de feas. Redondear en la función de cálculo hace que todas las conversiones salgan limpias sin acordarse de hacerlo fuera.
¿Por qué convertir devuelve el texto en vez de imprimirlo? Porque así sirve para más cosas: el programa principal lo imprime, pero podrías guardarlo en un archivo, enseñarlo en una ventana o comprobarlo en una prueba (convertir(1, 100) == "100 °C = 212.0 °F"). Una función que imprime solo sirve para imprimir.
¿Por qué escala_de_origen y es_temperatura_valida son funciones aparte? Porque responden preguntas que convertir no tiene por qué saber resolver. Si mañana añades la escala Rankine, cambias esas dos funciones y convertir apenas se toca.
¿Qué gana fahrenheit_a_kelvin llamando a otras dos? Que si alguna vez corriges la fórmula de °F a °C, la de °F a K se corrige sola. Con la fórmula copiada, tendrías dos sitios que arreglar y alguno se te olvidaría.
Comprobar la temperatura dentro de cada if de conversión. Acabas con la misma comprobación copiada seis veces. Compruébalo una vez, antes de convertir, como hace convertir.
Pruébalo tú
- Añade la opción 7, de K a °F, sin escribir ninguna fórmula nueva: compón
kelvin_a_celsiusycelsius_a_fahrenheit. - Cambia
conversion_completapara que también diga si la temperatura es de congelación (≤ 0 °C), normal o de ebullición (≥ 100 °C), con una función nueva que devuelva esa palabra.
Programa 2: mini calculadora modular
Una calculadora con seis operaciones (suma, resta, multiplicación, división, potencia y módulo). El enunciado de siempre, pero con una exigencia nueva: las funciones que calculan nunca imprimen y las que imprimen nunca calculan. Y tiene que aguantar dos casos incómodos: dividir (o hacer el módulo) entre cero y elegir una opción que no existe.
La idea
Tres capas, cada una con su responsabilidad:
- Calcular:
sumar,restar,multiplicar,dividir,potenciaymodulo. Reciben dos números y devuelven el resultado. Si no hay resultado posible (dividir entre cero), devuelvenNone. - Mostrar:
mostrar_menu,simboloymostrar_resultado. Son las únicas que saben cómo se ve la calculadora. - Coordinar:
calcularelige la función de cálculo según la opción yejecutarjunta una operación completa: comprueba, calcula y muestra.
Antes de programar, piensa en qué devuelve una función cuando «no puede». Aquí, None: no es un número, así que nadie lo confundirá con un resultado válido.
El código
# Programa 2 · Mini calculadora modular
# --- Funciones de cálculo: solo calculan y devuelven; nunca hacen print ---
def sumar(a, b):
return a + b
def restar(a, b):
return a - b
def multiplicar(a, b):
return a * b
def dividir(a, b):
if b == 0:
return None # None = "no hay resultado": quien llame decide qué decir
return round(a / b, 4)
def potencia(a, b):
return a ** b
def modulo(a, b):
if b == 0:
return None
return a % b
# --- Funciones de interfaz: solo se ocupan de lo que se ve ---
def mostrar_menu():
print("=== CALCULADORA MODULAR ===")
print("1. Suma 2. Resta 3. Multiplicar 4. Dividir 5. Potencia 6. Módulo")
def simbolo(opcion):
if opcion == 1:
return "+"
if opcion == 2:
return "-"
if opcion == 3:
return "×"
if opcion == 4:
return "/"
if opcion == 5:
return "^"
return "%"
def mostrar_resultado(a, signo, b, resultado):
if resultado is None:
print(f"{a} {signo} {b} → error: no se puede dividir entre cero")
else:
print(f"{a} {signo} {b} = {resultado}")
# --- Funciones que coordinan ---
def calcular(opcion, a, b):
"""Elige la función de cálculo según la opción y DEVUELVE su resultado."""
if opcion == 1:
return sumar(a, b)
if opcion == 2:
return restar(a, b)
if opcion == 3:
return multiplicar(a, b)
if opcion == 4:
return dividir(a, b)
if opcion == 5:
return potencia(a, b)
return modulo(a, b)
def ejecutar(opcion, a, b):
"""Una operación completa: comprueba, calcula con una función y muestra con otra."""
if opcion < 1 or opcion > 6:
print(f"Opción {opcion} no válida")
return # return sin valor: sale ya de la función
resultado = calcular(opcion, a, b)
mostrar_resultado(a, simbolo(opcion), b, resultado)
# --- Programa principal: las operaciones que antes se elegían en el menú ---
mostrar_menu()
ejecutar(1, 7, 2)
ejecutar(4, 7, 2)
ejecutar(4, 7, 0)
ejecutar(5, 2, 10)
ejecutar(6, 17, 5)
ejecutar(8, 1, 1)
Salida
=== CALCULADORA MODULAR === 1. Suma 2. Resta 3. Multiplicar 4. Dividir 5. Potencia 6. Módulo 7 + 2 = 9 7 / 2 = 3.5 7 / 0 → error: no se puede dividir entre cero 2 ^ 10 = 1024 17 % 5 = 2 Opción 8 no válida
Comprobamos: 7 + 2 = 9; 7 / 2 = 3.5; 2¹⁰ = 1024; 17 = 3 × 5 + 2, así que 17 % 5 = 2. La división entre cero no rompe el programa: dividir devuelve None y mostrar_resultado lo traduce a un mensaje. La opción 8 se rechaza antes de calcular nada.
Zoom: dividir y ejecutar
La pieza que hace que todo funcione es este contrato de dividir:
def dividir(a, b):
if b == 0:
return None # None = "no hay resultado": quien llame decide qué decir
return round(a / b, 4)
dividir no decide qué decirle al usuario: solo avisa de que no hay resultado devolviendo None. Y quien decide qué texto enseñar es mostrar_resultado, que pregunta if resultado is None. Se usa is None (y no == None) porque None es un valor único y is comprueba justo eso: si es ese objeto.
Y esta es la función que coordina:
def ejecutar(opcion, a, b):
"""Una operación completa: comprueba, calcula con una función y muestra con otra."""
if opcion < 1 or opcion > 6:
print(f"Opción {opcion} no válida")
return # return sin valor: sale ya de la función
resultado = calcular(opcion, a, b)
mostrar_resultado(a, simbolo(opcion), b, resultado)
Fíjate en el return solo, sin valor: sirve para salir de la función en cuanto la opción no vale, sin meter el resto del código dentro de un else. Devuelve None, pero aquí nadie usa lo que devuelve ejecutar, así que da igual.
Traza de ejecutar(4, 7, 0):
| Paso | Quién | Qué hace | Devuelve |
|---|---|---|---|
| 1 | ejecutar | 4 < 1 or 4 > 6 es False: sigue | — |
| 2 | calcular(4, 7, 0) | la opción 4 es dividir: llama a dividir(7, 0) | — |
| 3 | dividir(7, 0) | b == 0 | None |
| 4 | calcular | devuelve lo que le dio dividir | None |
| 5 | simbolo(4) | — | "/" |
| 6 | mostrar_resultado(7, "/", 0, None) | resultado is None: imprime el error | None |
El dibujo enseña el patrón con esa misma llamada: arriba coordina, a la izquierda se calcula (y se devuelve) y a la derecha se muestra (y se imprime):
Preguntas que te puedes hacer
¿Por qué tantas funciones para algo tan pequeño? Porque cada una se puede probar sola. dividir(7, 0) tiene que devolver None y dividir(7, 2) tiene que devolver 3.5: lo compruebas en dos líneas, sin menú, sin teclado y sin leer la pantalla. Es justo lo que no podía hacer con mi primera versión.
¿Por qué simbolo es una función y no un if dentro de mostrar_resultado? Porque el símbolo es un dato de presentación que también podría usar el menú o un historial. Además, mostrar_resultado queda corta y fácil de leer.
¿No sería más fácil que dividir imprimiera el error ella misma? Más fácil sí, mejor no. Si dividir imprime, ya no puedes usarla en un programa con ventanas o en una prueba sin que te ensucie la pantalla. Además, ¿qué devolvería? Tendrías que devolver algo de todas formas.
¿Y si la potencia es enorme? Python trabaja con enteros de cualquier tamaño: 2 ** 1000 funciona. Con decimales (2.0 ** 10000) sí obtendrías un OverflowError. Para FP1 basta con saber que existe.
Devolver un texto de error desde una función de cálculo (return "Error"). Quien la llama tiene que comparar textos para saber si salió bien, y si alguna vez suma ese «resultado», tendrás un TypeError lejísimos de su causa. Devuelve None (o, cuando lleguemos a ellas, lanza una excepción).
Pruébalo tú
- Añade la operación 7, división entera (
//), con su propia función que devuelvaNonesi el divisor es 0. ¿Cuántas funciones has tenido que tocar? - Haz que
mostrar_resultadoredondee a 2 decimales los resultados que no sean enteros, sin tocar ninguna función de cálculo.
Programa 3: piedra, papel o tijera con funciones
El juego contra la máquina. El jugador escribe su jugada como le da la gana (Piedra, " TIJERA ") y a veces con erratas (tijeras). Hay que limpiar lo que escribe, rechazar lo que no sea una jugada, decidir quién gana cada ronda y llevar el marcador. La máquina elige al azar.
La idea
Una función por responsabilidad, y una función principal, jugar, que solo coordina:
limpiar_jugada(texto): pasa a minúsculas, quita espacios y devuelve la jugada oNone.jugada_maquina(): devuelve una jugada al azar.determinar_ganador(jugador, maquina): devuelve"empate","jugador"o"maquina". No imprime.mostrar_rondaymostrar_final: las únicas que imprimen.
Para decidir quién gana usamos un diccionario, GANA_A, que dice a qué jugada le gana cada una. Los diccionarios los verás a fondo en contenedores asociativos; aquí basta con saber que GANA_A["piedra"] vale "tijera".
Antes de programar, piensa en qué partes del juego no dependen del azar. Esas (limpiar, decidir el ganador) son las que más fácil se prueban.
El código
# Programa 3 · Piedra, papel o tijera con funciones
import random
random.seed(7) # semilla fija: la máquina juega siempre lo mismo (quítala para jugar de verdad)
OPCIONES = ("piedra", "papel", "tijera")
GANA_A = {"piedra": "tijera", "papel": "piedra", "tijera": "papel"} # cada jugada → a cuál le gana
EMOJI = {"piedra": "🪨", "papel": "📄", "tijera": "✂️"}
def jugada_maquina():
return random.choice(OPCIONES)
def limpiar_jugada(texto):
"""Pasa a minúsculas y quita espacios. Devuelve la jugada, o None si no es válida."""
jugada = texto.lower().strip()
if jugada in OPCIONES:
return jugada
return None
def determinar_ganador(jugador, maquina):
"""Devuelve "empate", "jugador" o "maquina". No imprime nada."""
if jugador == maquina:
return "empate"
if GANA_A[jugador] == maquina:
return "jugador"
return "maquina"
def mostrar_ronda(numero, jugador, maquina, ganador):
print(f"Ronda {numero}: tú {EMOJI[jugador]} {jugador} · máquina {EMOJI[maquina]} {maquina}")
if ganador == "empate":
print(" → Empate")
elif ganador == "jugador":
print(" → ¡Ganas tú!")
else:
print(" → Gana la máquina")
def mostrar_final(victorias, derrotas, empates):
print(f"Marcador: tú {victorias} · máquina {derrotas} · empates {empates}")
if victorias > derrotas:
print("¡Ganaste la partida! 🏆")
elif derrotas > victorias:
print("La máquina gana la partida 🤖")
else:
print("Empate perfecto 🤝")
def jugar(entradas):
"""Una ronda por cada entrada válida; lleva el marcador y lo enseña al final."""
victorias = derrotas = empates = 0
ronda = 0
for texto in entradas:
jugador = limpiar_jugada(texto)
if jugador is None:
print(f"«{texto}» no es una jugada válida: no cuenta")
continue
ronda += 1
maquina = jugada_maquina()
ganador = determinar_ganador(jugador, maquina)
mostrar_ronda(ronda, jugador, maquina, ganador)
if ganador == "jugador":
victorias += 1
elif ganador == "maquina":
derrotas += 1
else:
empates += 1
mostrar_final(victorias, derrotas, empates)
# Lo que "teclea" el jugador: con mayúsculas, espacios y una errata, como en la vida real
print("=== PIEDRA, PAPEL O TIJERA ===")
jugar(["Piedra", "tijeras", "papel", " TIJERA ", "tijera"])
Salida
=== PIEDRA, PAPEL O TIJERA === Ronda 1: tú 🪨 piedra · máquina 📄 papel → Gana la máquina «tijeras» no es una jugada válida: no cuenta Ronda 2: tú 📄 papel · máquina 🪨 piedra → ¡Ganas tú! Ronda 3: tú ✂️ tijera · máquina 📄 papel → ¡Ganas tú! Ronda 4: tú ✂️ tijera · máquina ✂️ tijera → Empate Marcador: tú 2 · máquina 1 · empates 1 ¡Ganaste la partida! 🏆
Comprobamos ronda a ronda: piedra contra papel, gana el papel (la máquina); "tijeras" no es ninguna de las tres jugadas y no cuenta (fíjate en que no gasta una jugada de la máquina, porque jugada_maquina() se llama después del continue); papel contra piedra, ganas tú; " TIJERA " se limpia a tijera y gana al papel; tijera contra tijera, empate. Marcador 2-1 con un empate.
La línea random.seed(7) fija la «semilla» del azar, así que la máquina juega siempre papel, piedra, papel y tijera, y tu salida será igual que la del artículo. Para jugar de verdad, bórrala.
Zoom: limpiar y decidir
def limpiar_jugada(texto):
"""Pasa a minúsculas y quita espacios. Devuelve la jugada, o None si no es válida."""
jugada = texto.lower().strip()
if jugada in OPCIONES:
return jugada
return None
def determinar_ganador(jugador, maquina):
"""Devuelve "empate", "jugador" o "maquina". No imprime nada."""
if jugador == maquina:
return "empate"
if GANA_A[jugador] == maquina:
return "jugador"
return "maquina"
limpiar_jugada encadena lower() (todo a minúsculas) y strip() (quita espacios al principio y al final). Los métodos de los textos los verás en el tema de strings; aquí los usamos como herramienta. Lo importante es la forma: devuelve la jugada limpia o None, y quien la llama decide qué hacer con un None.
determinar_ganador resuelve el juego en tres líneas gracias al diccionario. El dibujo enseña el ciclo que guarda GANA_A:
jugador | maquina | ¿Iguales? | GANA_A[jugador] | ¿== maquina? | Devuelve |
|---|---|---|---|---|---|
"piedra" | "papel" | no | "tijera" | no | "maquina" |
"papel" | "piedra" | no | "piedra" | sí | "jugador" |
"tijera" | "papel" | no | "papel" | sí | "jugador" |
"tijera" | "tijera" | sí | — | — | "empate" |
Preguntas que te puedes hacer
¿Por qué jugar recibe una lista de entradas en lugar de pedirlas con input()? Para que el programa se pueda ejecutar y comprobar siempre igual. Para jugar de verdad, cambiarías el bucle for texto in entradas por un input() dentro de un while, y ninguna otra función tendría que cambiar. Esa es la gracia de separarlo todo.
¿Por qué victorias, derrotas y empates son locales de jugar? Porque solo jugar los necesita mientras dura la partida. Si fueran globales, cualquier función podría tocarlos y una segunda partida empezaría con el marcador sucio.
¿Qué hace victorias = derrotas = empates = 0? Asigna 0 a las tres variables en una línea. Funciona porque los números no se pueden modificar «por dentro»; con listas no lo hagas, que acabarían siendo la misma lista.
¿Por qué continue en una jugada no válida? Porque esa entrada no debe contar como ronda: continue salta a la siguiente vuelta del for sin pasar por el resto. Por eso las rondas van numeradas 1, 2, 3 y 4 aunque haya cinco entradas.
Llamar a jugada_maquina() antes de comprobar si la jugada del jugador es válida. La máquina «gasta» jugadas en rondas que no existen y, con la semilla fija, todas las rondas siguientes cambian.
Pruébalo tú
- Acepta abreviaturas: que
limpiar_jugadaconvierta"p","pa"y"t"en"piedra","papel"y"tijera", sin tocar ninguna otra función. - Añade lagarto y Spock (cada jugada gana a dos y pierde contra dos). Pista: el valor de
GANA_Apuede ser una lista de dos jugadas y la comprobación pasa a sermaquina in GANA_A[jugador].
El patrón que debes llevarte
Los tres programas tienen la misma forma, y es la que vas a usar en todas las prácticas de GCID a partir de ahora:
| Tipo de función | Qué hace | Qué nunca hace | En los programas |
|---|---|---|---|
| De cálculo | recibe datos y devuelve un resultado (o None) | imprimir o pedir datos | celsius_a_fahrenheit, dividir, determinar_ganador |
| De comprobación | responde una pregunta con True/False o un valor | cambiar nada | es_temperatura_valida, limpiar_jugada |
| De presentación | recibe resultados y los enseña | calcular | mostrar_resultado, mostrar_ronda |
| Coordinadora | llama a las demás en orden | contener fórmulas | convertir, ejecutar, jugar |
Cuando tengas dudas sobre dónde va una línea, pregúntate: ¿esto calcula o esto enseña? Si hace las dos cosas, parte la función en dos.
Errores típicos en esta práctica
- Funciones de cálculo que imprimen. Funcionan una vez y estorban siempre: no se pueden probar ni reutilizar.
- Usar el resultado de una función que solo imprime. La variable vale
Noney el error salta en otra línea. - Copiar una fórmula en lugar de componer funciones. Al corregir una, la copia se queda mal.
- Devolver textos de error desde el cálculo. Mejor
None, y que la presentación decida el mensaje. - Comprobar con
== None. Funciona casi siempre, pero lo correcto esis None. - Marcadores o contadores globales. Mételos en la función que los usa y pásalos o devuélvelos cuando haga falta.
Míralo paso a paso en Python Tutor
Python Tutor dibuja cada llamada como una caja con sus variables. Elige Python 3 y pega esta versión mínima del juego, con dos rondas fijas:
GANA_A = {"piedra": "tijera", "papel": "piedra", "tijera": "papel"}
def determinar_ganador(jugador, maquina):
if jugador == maquina:
return "empate"
if GANA_A[jugador] == maquina:
return "jugador"
return "maquina"
def sumar_punto(ganador, victorias):
if ganador == "jugador":
victorias = victorias + 1
return victorias
victorias = 0
g = determinar_ganador("papel", "piedra")
victorias = sumar_punto(g, victorias)
g = determinar_ganador("piedra", "papel")
victorias = sumar_punto(g, victorias)
print(g, victorias)
Salida esperada:
maquina 1
Qué observar paso a paso:
- Al llamar a
determinar_ganador("papel", "piedra")se abre una caja conjugadorymaquina. Mira cómo, en la tercera línea de la función, el Return value es"jugador"y la caja se cierra. - En
sumar_punto(g, victorias)hay dos variables llamadasvictorias: la del Global frame (0) y la local de la caja. Cuando la local pasa a 1, la global sigue en 0 hasta que elreturny la asignaciónvictorias = sumar_punto(...)la actualizan. - En la segunda ronda,
GANA_A["piedra"]vale"tijera", que no es"papel": la función sale por el últimoreturncon"maquina", ysumar_puntodevuelve lavictoriasque recibió, sin cambiarla. - Fíjate en que
GANA_Aestá en el Global frame y las funciones la leen sin pasarla como parámetro: es una constante, nadie la cambia.
Resumen y siguiente paso
- Las funciones de cálculo reciben, calculan y devuelven: ni
input()niprint. - Componer funciones (
celsius_a_kelvin(fahrenheit_a_celsius(f))) evita copiar fórmulas. - Cuando no hay resultado posible, se devuelve
Noney se comprueba conis None. - Un
returndentro de unifcorta la función: cada rama con sureturnno necesitaelif. - Una función coordinadora llama a las demás en orden y es la única que conoce el flujo completo.
- Si los datos llegan como parámetros, el programa se puede probar siempre igual.
Checklist: ¿lo has practicado?
- ¿Has ejecutado los tres programas y comprobado las salidas?
- ¿Sabrías decir qué funciones de los tres programas imprimen y cuáles devuelven?
- ¿Puedes explicar por qué
fahrenheit_a_kelvinno tiene fórmula propia? - ¿Entiendes por qué
dividirdevuelveNoney quién decide el mensaje de error? - ¿Has intentado los «Pruébalo tú» sin mirar la teoría?
- ¿Podrías cambiar la entrada del juego a
input()sin tocardeterminar_ganador?
Si has respondido que sí a casi todo, estás listo para los ejercicios de funciones en Python, donde ya no te doy el código hecho. ¡Nos vemos allí!
Siguiente paso: ejerciciosFunciones en Python — ejercicios con notas, un mini banco y dados con estadísticas (y el return que se rindió en la primera tirada)¿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)