Saltar al contenido
Volver al inicio 100 Días de Código en Python — Día 12: Ámbito (Scope) y el Juego de Adivinar el Número

100 Días de Código en Python — Día 12: Ámbito (Scope) y el Juego de Adivinar el Número

Nivel: Grado décimo Área: Tecnología e Informática Modalidad: Tutorial práctico Fuente: Curso "100 Days of Code: The Complete Python Pro Bootcamp" (Angela Yu, App Brewery). Contenido del Día 12.

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

  1. Antes de empezar
  2. Ámbito local y ámbito global (espacios de nombres)
  3. ¿Tiene Python ámbito de bloque?
  4. Modificar una variable global (y por qué evitarlo)
  5. Constantes globales
  6. 🚀 Proyecto del día: Adivina el Número
  7. 🌟 Proyecto extra: El Computador Adivina tu Número
  8. 🏆 Proyecto integrador del Día 12: Torneo de Adivinanzas del Curso
  9. 🤖 Programar con apoyo de IA
  10. 🛠️ Correcciones técnicas al material original
  11. 📋 Matriz de trazabilidad
  12. 📦 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) y random.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:

Python
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:

Python
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:

Python
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 de principal() 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 x en los tres primeros niveles. ¿Cuál se imprime en cada caso?

🧩 Comprueba lo aprendido: Tema 1

  • Predicción: ¿qué imprime este código?
Python
  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.

Python
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 fuera

Dentro 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:

Python
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 for sigue existiendo después del bucle. ¿Qué valor tiene? (Pista: el último del recorrido.)
  • 🟡 Reto 2: provoca y luego corrige el UnboundLocalError del ejemplo create_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 un else?
  • 🔴 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?

Python
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):

Python
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):

Python
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 puntaje para que use return.
  • 🟡 Reto 2: crea restar_vida(vidas) y sumar_puntos(puntaje, puntos) con return, 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:

Python
PI = 3.14159
URL_COLEGIO = "https://www.micolegio.edu.co"
EASY_LEVEL_TURNS = 10
HARD_LEVEL_TURNS = 5

Al 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)

  1. Mostrar una bienvenida y "Estoy pensando en un número entre 1 y 100".
  2. Pedir la dificultad: easy (10 intentos) o hard (5 intentos).
  3. En cada turno, mostrar los intentos restantes y pedir un número.
  4. Responder "Demasiado alto" o "Demasiado bajo" y restar un intento, o "¡Lo lograste! La respuesta era X".
  5. 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:

  1. Elegir un número al azar entre 1 y 100.
  2. Una función para elegir la dificultad.
  3. Dejar que el usuario haga un intento.
  4. Una función para comparar el intento con la respuesta.
  5. Llevar la cuenta de los intentos y restar 1 si falla.
  6. 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 print de los intentos restantes debe ir dentro del while. 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_answer no tiene return en esa rama y devuelve None, así que turns pasa a valer None. El programa no falla, porque el bucle termina justo después, pero es un caso del error "un camino sin return" (Día 10, Tema 2). Mejora: devuelve turns tambié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:

Python
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.py como 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

  1. Variables minimo = 1 y maximo = 100, locales dentro de una función jugar().
  2. Una función punto_medio(minimo, maximo) que devuelva (minimo + maximo) // 2.
  3. Un while que 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.
  4. Contar los intentos y mostrarlos al final.
  5. Detectar si el usuario hace trampa: si minimo > maximo, mostrar "¡Alguien está mintiendo!".
  6. 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

  1. Constantes globales para los intentos por nivel, los puntos por intento sobrante y el rango de números.
  2. 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.
  3. pedir_numero(minimo, maximo) devuelve un número válido. Vuelve a preguntar si el valor está fuera del rango.
  4. actualizar_tabla(tabla, nombre, puntos) devuelve la tabla actualizada. La tabla es un diccionario {nombre: puntos_acumulados}.
  5. mostrar_tabla(tabla) imprime las posiciones de mayor a menor. (Investiga sorted con key, o busca el máximo repetidamente con lo aprendido en el Día 5.)
  6. Un programa principal con while que pregunte el siguiente jugador hasta que se escriba fin.
  7. 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 global en 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:

  1. No pidas el juego completo. Haz tu lista de tareas y tu código.
  2. 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?".
  3. 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: NameError por ámbito local, UnboundLocalError (al modificar una global o por una variable creada solo dentro de un if) y el None por un camino sin return.