Este tutorial adapta pedagógicamente el Día 12. Las secciones 📝 Ejercicio original del curso provienen de los archivos del curso. Los retos 🟢🟡🟠🔴, el proyecto extra y el proyecto integrador son creaciones de esta adaptación. Las correcciones al material original aparecen como 🛠️ Nota técnica.
Tabla de contenidos
- Antes de empezar
- Ámbito local y ámbito global (espacios de nombres)
- ¿Tiene Python ámbito de bloque?
- Modificar una variable global (y por qué evitarlo)
- Constantes globales
- 🚀 Proyecto del día: Adivina el Número
- 🌟 Proyecto extra: El Computador Adivina tu Número
- 🏆 Proyecto integrador del Día 12: Torneo de Adivinanzas del Curso
- 🤖 Programar con apoyo de IA
- 🛠️ Correcciones técnicas al material original
- 📋 Matriz de trazabilidad
- 📦 Evidencias de aprendizaje del Día 12
0. Antes de empezar
(Fuentes: 086 - Day 12 Goals- What we'll make by the end of the day.md, 093 - Don't be too hard on yourself.md)
Hoy aprenderemos sobre el ámbito (scope): desde dónde se puede usar cada variable o función. Ya nos cruzamos con este tema en los Días 8 y 11, con advertencias como "shadows name from outer scope". Hoy lo entenderemos a fondo.
El proyecto: Adivina el Número. El computador piensa un número del 1 al 100 y tú tienes 10 intentos (nivel fácil) o 5 (nivel difícil). Después de cada intento, el juego te dice si tu número fue "demasiado alto" o "demasiado bajo".
Mensaje de cierre del curso: no seas tan duro contigo. Saltarte un día o no encontrar la respuesta a la primera no es fracasar. Mientras sigas intentándolo, estás avanzando.
Prerequisito general: funciones con entradas y salidas (Días 8 y 10),
while(Día 6) yrandom.randint(Día 4).
1. Ámbito local y ámbito global
(Fuente: 087 - Namespaces- Local vs. Global Scope.md)
🎯 Objetivo de aprendizaje
- Distinguir entre ámbito local (dentro de una función) y ámbito global (nivel superior del archivo).
- Predecir dónde se puede usar una variable o una función según dónde fue creada.
📖 Explicación conceptual
Analogía del curso: un manzano dentro del patio cercado de tu casa: solo tú y tu familia pueden tomar sus manzanas (ámbito local). Un manzano en la acera: cualquiera puede tomarlas (ámbito global).
Ámbito local. Lo que se crea dentro de una función solo existe dentro de ella:
def drink_potion():
potion_strength = 2
print(potion_strength) # ✅ 2
drink_potion()
print(potion_strength) # ❌ NameError: name 'potion_strength' is not definedÁmbito global. Lo que se crea en el nivel superior del archivo, es decir, fuera de cualquier función, se puede leer desde cualquier parte:
player_health = 10
def drink_potion():
potion_strength = 2
print(player_health) # ✅ 10: una variable global se puede leer dentro de la función
drink_potion()"Nivel superior" no significa "arriba del archivo": significa "sin sangría", fuera de cualquier función.
Espacios de nombres (namespaces). Todo lo que tiene nombre (variables, funciones…) tiene un ámbito. Si defines drink_potion dentro de otra función game(), solo se puede llamar desde dentro de game().
📝 Ejercicio original del curso: el misterio de los enemigos
Enunciado: antes de ejecutarlo, predice qué imprime cada print:
enemies = 1
def increase_enemies():
enemies = 2
print(f"enemigos dentro de la función: {enemies}")
increase_enemies()
print(f"enemigos fuera de la función: {enemies}")Resultado real (verificado): dentro: 2 y fuera: 1.
¿Por qué? La línea enemies = 2 dentro de la función no modifica la variable global: crea una variable local nueva que se llama igual. Son dos variables distintas. El curso lo hace evidente usando "Skeleton" para una y "Zombie" para la otra.
⚠️ Errores frecuentes
- Usar fuera de la función una variable creada dentro →
NameError. - Creer que asignar dentro de la función cambia la variable global: crea una local con el mismo nombre. El curso califica de "pésima idea" usar el mismo nombre para variables locales y globales.
Retos adicionales (creados para esta adaptación)
- 🟢 Reto 1: escribe un programa con una variable global y otra local, e imprime ambas desde dentro y desde fuera de la función. Anota cuáles producen error.
- 🟡 Reto 2: define una función
ayudante()dentro deprincipal()e intenta llamarla desde fuera. Explica el error. - 🟠 Reto 3: revisa tu Blackjack (Día 11) o tu Calculadora (Día 10) y haz una tabla con cada variable, dónde se creó y su ámbito (local o global).
- 🔴 Reto 4: Python busca los nombres en este orden: Local → Enclosing (la función que contiene a otra) → Global → Built-in (lo que viene con Python). Es la regla LEGB. Investígala y crea un ejemplo con una variable
xen los tres primeros niveles. ¿Cuál se imprime en cada caso?
🧩 Comprueba lo aprendido: Tema 1
- Predicción: ¿qué imprime este código?
mensaje = "global"
def f():
mensaje = "local"
return mensaje
print(f())
print(mensaje)- Conceptual: explica la analogía de los manzanos con tus palabras.
2. ¿Tiene Python ámbito de bloque?
(Fuente: 088 - Does Python Have Block Scope-.md)
📖 Explicación conceptual
En otros lenguajes, como C++ o Java, una variable creada dentro de un if o de un for solo existe dentro de ese bloque. En Python no es así: los bloques if, for y while no crean un ámbito nuevo. Solo las funciones lo hacen.
game_level = 3
enemies = ["Skeleton", "Zombie", "Alien"]
if game_level < 5:
new_enemy = enemies[0]
print(new_enemy) # ✅ Skeleton: se creó dentro del if, pero es visible fueraDentro de una función, esa misma variable es local a la función, aunque se haya creado dentro de un if de esa función.
La advertencia del editor. Si la variable solo se crea cuando se cumple una condición, el editor avisa: "Local variable might be referenced before assignment". Si la condición no se cumple, la variable nunca se crea:
def create_enemy(level):
if level < 5:
new_enemy = "Skeleton"
return new_enemy
create_enemy(10) # ❌ UnboundLocalError (verificado)Solución del curso: inicializar la variable antes del bloque, por ejemplo new_enemy = "". Es lo mismo que hicimos en el Blackjack con computer_score = -1.
Retos adicionales (creados para esta adaptación)
- 🟢 Reto 1: comprueba que una variable creada dentro de un
forsigue existiendo después del bucle. ¿Qué valor tiene? (Pista: el último del recorrido.) - 🟡 Reto 2: provoca y luego corrige el
UnboundLocalErrordel ejemplocreate_enemy(10). - 🟠 Reto 3: escribe
clasificar(nota)de forma que siempre devuelva algo, aunque la nota esté fuera del rango. ¿Qué estrategia usaste: inicializar la variable o poner unelse? - 🔴 Reto 4: busca en internet un ejemplo de ámbito de bloque en JavaScript (
let) y compáralo con Python. Escribe un párrafo sobre las ventajas de cada enfoque.
3. Modificar una variable global
(Fuente: 089 - How to Modify a Global Variable.md)
📖 Explicación conceptual
¿Y si de verdad quieres cambiar la variable global desde una función?
enemies = 1
def increase_enemies():
enemies += 1 # ❌ UnboundLocalError (verificado)Python ve que se asigna enemies dentro de la función y la trata como local, pero esa variable local no tiene un valor previo al que sumarle 1.
Opción 1: la palabra clave global (funciona, pero no se recomienda):
def increase_enemies():
global enemies
enemies += 1¿Por qué evitarla? La variable global puede haberse creado en cualquier parte del programa, incluso días antes de escribir la función. Modificarla "a distancia" hace que el código sea confuso y propenso a errores. Leer una variable global está bien; modificarla desde una función es lo que conviene evitar.
Opción 2 (recomendada): devolver el nuevo valor con return (Día 10):
def increase_enemies(enemy_count):
return enemy_count + 1
enemies = increase_enemies(enemies)Así, la función no necesita saber nada del resto del programa: recibe un dato, devuelve un resultado y se puede reutilizar en cualquier parte.
⚠️ Errores frecuentes
| Situación | Resultado |
|---|---|
x += 1 dentro de una función, con x global y sin global |
UnboundLocalError |
x = 5 dentro de una función, con x global |
Crea una x local; la global no cambia |
Leer x global dentro de una función |
✅ Funciona |
Retos adicionales (creados para esta adaptación)
- 🟢 Reto 1: reescribe una función con
global puntajepara que usereturn. - 🟡 Reto 2: crea
restar_vida(vidas)ysumar_puntos(puntaje, puntos)conreturn, y úsalas en un bucle que simule 5 rondas de un juego. - 🟠 Reto 3: revisa tu Ahorcado (Día 7): ¿qué variables se modifican? Reescribe una parte usando funciones que devuelvan valores.
- 🔴 Reto 4: escribe un párrafo, con un ejemplo, sobre un error que podría aparecer en un programa grande si varias funciones modifican la misma variable global.
4. Constantes globales
(Fuente: 090 - Python Constants and Global Scope.md)
📖 Explicación conceptual
"No creas a quien te diga que nunca debes usar el ámbito global", dice el curso. El ámbito global es muy útil para las constantes: valores que se definen una vez y no se piensan cambiar, como el valor de π, una URL o un número de intentos.
Convención de Python: las constantes se escriben en MAYÚSCULAS con guiones bajos:
PI = 3.14159
URL_COLEGIO = "https://www.micolegio.edu.co"
EASY_LEVEL_TURNS = 10
HARD_LEVEL_TURNS = 5Al verlas en mayúsculas, sabes que no debes modificarlas. Python no impide cambiarlas: es un acuerdo entre programadores.
Ventaja práctica (se ve en el proyecto): si al probar el juego descubres que 5 intentos son muy pocos, cambias una sola línea al inicio del archivo y el cambio se refleja en todo el programa.
📝 Ejercicio original del curso: cuestionario de ámbito
La lección remite a un cuestionario sobre ámbito. Su contenido no está incluido entre los archivos suministrados. Las secciones 🧩 de los Temas 1 a 3 cubren el mismo objetivo.
Retos adicionales (creados para esta adaptación)
- 🟢 Reto 1: identifica en tus proyectos anteriores 3 valores que deberían ser constantes y conviértelos.
- 🟡 Reto 2: en tu calculadora de notas, convierte los pesos (30 %, 40 % y 30 %) y la nota mínima (3.0) en constantes.
5. 🚀 Proyecto del día: Adivina el Número
(Fuentes: 091 - Introducing the Final Project- The Number Guessing Game.md, 092 - Solution & Walkthrough to the Number Guessing Game.md, proyecto oficial del curso original)
Lo especial de este proyecto
El curso dice: "ahora vuelas solo". No hay código inicial, ni comentarios, ni TODO. Solo un enlace para jugar la versión final. La instrucción es: juega varias veces, haz tu propia lista de tareas, conviértela en comentarios y después escribe el código, probando todo.
Requisitos funcionales (observados en la demostración)
- Mostrar una bienvenida y "Estoy pensando en un número entre 1 y 100".
- Pedir la dificultad:
easy(10 intentos) ohard(5 intentos). - En cada turno, mostrar los intentos restantes y pedir un número.
- Responder "Demasiado alto" o "Demasiado bajo" y restar un intento, o "¡Lo lograste! La respuesta era X".
- Si se acaban los intentos: "Te quedaste sin intentos, perdiste".
🛠️ Nota técnica: en la lección
091, la transcripción dice al final "el nivel fácil tiene 5 intentos, el difícil 10", al revés de lo que muestran la demostración y la solución. Lo correcto es fácil = 10 y difícil = 5.
🔍 Antes de programar: descomposición (como la hace el curso)
Lista de componentes que construye el curso a partir de la demostración:
- Elegir un número al azar entre 1 y 100.
- Una función para elegir la dificultad.
- Dejar que el usuario haga un intento.
- Una función para comparar el intento con la respuesta.
- Llevar la cuenta de los intentos y restar 1 si falla.
- Repetir mientras queden intentos y no haya acertado.
Escribe tu lista antes de mirar esta.
Construcción paso a paso (siguiendo la solución del curso)
Versión 1: from random import randint y answer = randint(1, 100) (incluye 1 y 100). Imprime la respuesta solo mientras pruebas.
Versión 2: check_answer(guess, answer). Con if mayor que / elif menor que / else: acertó.
Versión 3: set_difficulty() con constantes. EASY_LEVEL_TURNS = 10 y HARD_LEVEL_TURNS = 5. La función devuelve el número de intentos, en lugar de modificar una variable global.
- El curso detecta un error de orden al probar: pedía el número antes de la dificultad. Probar a menudo revela estos errores.
Versión 4: repetir. while guess != answer:. El curso descubre que guess debe inicializarse antes del while (guess = 0), y mete todo el juego en una función game() para que las variables sean locales.
Versión 5: contar intentos sin global. El curso considera usar global turns y lo descarta. En su lugar, check_answer(guess, answer, turns) devuelve turns - 1 cuando el intento es incorrecto, y se usa turns = check_answer(guess, answer, turns).
- El
printde los intentos restantes debe ir dentro delwhile. Si no, solo se muestra una vez.
💡 Recomendación del curso: esta parte "es un poco alucinante". Ejecútala paso a paso en Python Tutor (se abre en otra pestaña) para ver cómo cambia
turns.
Versión 6: perder. Si turns == 0, se imprime "Te quedaste sin intentos" y se usa return para salir de game(). Es el retorno anticipado del Día 10.
Versión 7 (opcional): mostrar "Adivina otra vez" solo cuando quedan intentos y no acertó.
🛠️ Nota técnica (verificada): en la solución del curso, cuando el usuario acierta,
check_answerno tienereturnen esa rama y devuelveNone, así queturnspasa a valerNone. El programa no falla, porque el bucle termina justo después, pero es un caso del error "un camino sinreturn" (Día 10, Tema 2). Mejora: devuelveturnstambién en la rama del acierto.
Pruebas
(Durante las pruebas, imprime la respuesta o fíjala temporalmente, por ejemplo answer = 42.)
| Dificultad | Intentos | Resultado esperado |
|---|---|---|
| easy | 50, 25, 42 | "Demasiado alto", "Demasiado bajo", "¡Lo lograste!" |
| hard | 1, 2, 3, 4, 5 | Cinco veces "Demasiado bajo" y luego "Perdiste" |
| EASY (en mayúsculas) | — | ⚠️ En la versión del curso cae en el else y da 5 intentos. Mejora: .lower() y validar |
| easy | cincuenta |
⚠️ ValueError (el manejo de excepciones se ve más adelante en el curso) |
| easy | 150 | ¿Debería contar como intento? Decide y valida |
🧪 Prueba y depuración
Este código tiene cuatro errores:
from random import randint
EASY_LEVEL_TURNS = 10
HARD_LEVEL_TURNS = 5
def check_answer(guess, answer, turns):
if guess > answer:
print("Demasiado alto.")
return turns - 1
elif guess < answer:
print("Demasiado bajo.")
else:
print(f"¡Lo lograste! La respuesta era {answer}.")
def set_difficulty():
level = input("Elige dificultad 'easy' o 'hard': ")
if level == "easy":
turns = EASY_LEVEL_TURNS
else:
turns = HARD_LEVEL_TURNS
def game():
answer = randint(1, 100)
turns = set_difficulty()
while guess != answer:
print(f"Te quedan {turns} intentos.")
guess = int(input("Haz un intento: "))
turns = check_answer(guess, answer, turns)
if turns == 0:
print("Te quedaste sin intentos. Perdiste.")
return
game()(Pistas: una rama sin return que no es la del acierto, una función que no devuelve nada, una variable que se usa antes de existir y la rama del acierto que devuelve None. Esta última no es un error grave, pero es una mala práctica.)
⭐ Extensión del proyecto
- Crear tu logo en arte ASCII con un generador de "texto a ASCII", como propone el curso, y guardarlo en
art.pycomo un string de triple comilla. - Preguntar "¿Jugar otra vez?".
- Mostrar el historial de intentos al final.
- Reflexión matemática: ¿cuántos intentos necesitas, como máximo, si siempre eliges el punto medio del rango posible? Ver el proyecto extra.
6. 🌟 Proyecto extra: El Computador Adivina tu Número
Situación
Ahora los papeles se invierten: tú piensas un número del 1 al 100 y el computador debe adivinarlo. Tú solo respondes "alto", "bajo" o "correcto".
La estrategia: búsqueda binaria
Si en cada intento el computador elige el punto medio del rango que todavía es posible, descarta la mitad de los números cada vez. Con esta estrategia:
- Con 7 intentos siempre adivina cualquier número del 1 al 100, porque 2⁷ − 1 = 127 ≥ 100 (verificado).
- Con 5 intentos solo puede garantizar rangos de hasta 31 números.
- Conclusión: en el modo difícil del proyecto del día, ganar depende en parte de la suerte.
Requisitos
- Variables
minimo = 1ymaximo = 100, locales dentro de una funciónjugar(). - Una función
punto_medio(minimo, maximo)que devuelva(minimo + maximo) // 2. - Un
whileque proponga un número y actualice el rango según la respuesta:- Si la respuesta es "alto":
maximo = intento - 1. - Si es "bajo":
minimo = intento + 1.
- Si la respuesta es "alto":
- Contar los intentos y mostrarlos al final.
- Detectar si el usuario hace trampa: si
minimo > maximo, mostrar "¡Alguien está mintiendo!". - Sin
global.
Casos de prueba
| Número pensado | Intentos del computador (máximo) |
|---|---|
| 50 | 1 |
| 1 | ≤ 7 |
| 100 | ≤ 7 |
| Respuestas contradictorias | "¡Alguien está mintiendo!" |
Reflexión
¿Dónde más se usa la búsqueda binaria? Piensa en buscar una palabra en un diccionario de papel o una página en un libro.
7. 🏆 Proyecto integrador del Día 12: Torneo de Adivinanzas del Curso
Proyecto integrador creado para esta adaptación. Integra los Días 1 a 12, con énfasis en el buen manejo del ámbito: constantes globales y datos que viajan entre funciones con parámetros y
return.
Situación problema
El curso organiza un torneo de "Adivina el Número" en la hora de Tecnología. Varios estudiantes juegan por turnos y se lleva una tabla de posiciones.
Pregunta orientadora
¿Cómo diseñar un programa con varias funciones que compartan información sin usar variables globales modificables?
Requerimientos
- Constantes globales para los intentos por nivel, los puntos por intento sobrante y el rango de números.
jugar_ronda(nombre, nivel)devuelve los puntos obtenidos: 0 si pierde; si gana, los intentos sobrantes multiplicados por la constante de puntos, más un bonus en el nivel difícil.pedir_numero(minimo, maximo)devuelve un número válido. Vuelve a preguntar si el valor está fuera del rango.actualizar_tabla(tabla, nombre, puntos)devuelve la tabla actualizada. La tabla es un diccionario{nombre: puntos_acumulados}.mostrar_tabla(tabla)imprime las posiciones de mayor a menor. (Investigasortedconkey, o busca el máximo repetidamente con lo aprendido en el Día 5.)- Un programa principal con
whileque pregunte el siguiente jugador hasta que se escribafin. - Prohibido usar
global. Todas las funciones tienen docstring.
Entregables
- Código funcional.
- Un mapa de ámbitos: una tabla con cada variable, la función donde vive y cómo llega su valor a otras funciones.
- Tabla de pruebas.
Criterios técnicos
- No aparece la palabra
globalen el código. - Cambiar una constante (por ejemplo, el rango a 1–50) modifica el juego sin tocar ninguna otra línea.
- Los puntajes de la tabla coinciden con los calculados a mano en 3 rondas de prueba.
8. 🤖 Programar con apoyo de IA
El curso insiste en que este proyecto se haga solo: "sé que hay muchas maneras de hacer trampa, pero no lo hagas, ten fe en ti mismo". Una IA es una de esas maneras.
Uso recomendado hoy:
- No pidas el juego completo. Haz tu lista de tareas y tu código.
- Cuando termines, pídele a la IA que revise el ámbito de tu código: "¿Uso variables globales de forma innecesaria? ¿Hay algún camino sin
return?". - Actividad de verificación: pregunta a una IA si Python tiene ámbito de bloque. Comprueba su respuesta con el ejemplo del Tema 2 y compárala con la explicación del curso.
9. 🛠️ Correcciones técnicas al material original
| Archivo | Afirmación o comportamiento del curso | Observación (verificada) | Tratamiento |
|---|---|---|---|
091 |
"El nivel fácil tiene 5 intentos, el difícil 10" | Está invertido: la demostración y la solución usan fácil = 10 y difícil = 5 | Nota técnica en el proyecto |
092 |
check_answer sin return en la rama del acierto |
Devuelve None y turns pasa a ser None. Funciona, pero es una mala práctica |
Nota técnica y depuración |
092 |
set_difficulty usa else para "hard" |
Cualquier entrada distinta de "easy" (incluida "EASY") da el nivel difícil | Tabla de pruebas |
089 |
"Sin esta línea no podemos modificar algo global" | Correcto. El error concreto es UnboundLocalError |
Tema 3 |
090 |
Cuestionario de ámbito | No está incluido entre los archivos suministrados | Secciones 🧩 equivalentes |
10. 📋 Matriz de trazabilidad
| Contenido original | Archivo de origen | Concepto desarrollado | Ejercicio original | Reto adicional | Proyecto relacionado |
|---|---|---|---|---|---|
| Objetivos: Adivina el Número | 086 |
Sección 0 | — | — | Sección 5 |
| Ámbito local y global, espacios de nombres, manzanos | 087 |
Tema 1 | El misterio de los enemigos | Retos 1–4 del Tema 1 (LEGB) | Secciones 5 y 7 |
| Sin ámbito de bloque, variable posiblemente sin asignar | 088 |
Tema 2 | Ejemplo new_enemy |
Retos 1–4 del Tema 2 | Sección 5 |
global y la alternativa con return |
089 |
Tema 3 | increase_enemies con global y con return |
Retos 1–4 del Tema 3 | Secciones 5, 6 y 7 |
| Constantes en mayúsculas | 090 |
Tema 4 | Cuestionario (mencionado, no suministrado) | Retos 1–2 del Tema 4 | Secciones 5 y 7 |
| Proyecto sin código inicial: descomposición | 091 |
Sección 5 | Jugar la demostración y hacer la lista de tareas | — | Sección 5 |
| Solución paso a paso | 092 |
Sección 5 | Proyecto oficial | Depuración con 4 errores y extensiones | Sección 5 |
| No seas tan duro contigo | 093 |
Sección 0 | — | — | — |
| (No existe en el curso original) | — | — | — | El Computador Adivina (búsqueda binaria) | Sección 6 |
| (No existe en el curso original) | — | — | — | Torneo de Adivinanzas | Sección 7 |
11. 📦 Evidencias de aprendizaje del Día 12
- Tu propia lista de tareas del proyecto, escrita antes de programar.
- Adivina el Número funcionando, con la tabla de pruebas y al menos una validación agregada.
- Respuesta explicada al "misterio de los enemigos".
- Uno de los proyectos adicionales. En el integrador, incluye el mapa de ámbitos.
- Cuaderno de errores:
NameErrorpor ámbito local,UnboundLocalError(al modificar una global o por una variable creada solo dentro de unif) y elNonepor un camino sinreturn.