Esta lección cierra el módulo 2 atando dos cabos que llevamos arrastrando desde hace tiempo.
El primero se descubrió con lápiz y papel, en la prueba de escritorio de la lección Del problema al algoritmo: cuando Marta tecleaba ALTA en mayúsculas, el algoritmo rechazaba una prioridad perfectamente correcta, porque para un ordenador "ALTA" y "alta" son dos textos distintos. Prometimos resolverlo aquí.
El segundo apareció en la lección anterior: input() devuelve siempre un str, así que en cuanto TareaFácil quiera pedir los días estimados o las horas dedicadas y hacer cuentas con ellos, se estrellará con un TypeError. Por eso esos datos siguen escritos a mano en el código.
Ambos son manifestaciones del mismo principio, que es la idea de fondo de esta lección: todo dato que viene de fuera del programa es sospechoso hasta que se demuestre lo contrario. Puede tener el tipo equivocado, sobrarle espacios, venir en mayúsculas, estar vacío o ser directamente un disparate. El trabajo de convertirlo al tipo correcto se llama conversión; el de comprobar que tiene sentido, validación. Un programa que no hace las dos cosas no es un programa terminado: es un prototipo que funciona mientras nadie lo use en serio.
Aprenderás a convertir con int(), float(), str() y bool(), a interpretar los errores ValueError y TypeError, a sanear texto con .strip(), .lower() y .capitalize(), y a comprobar datos antes de usarlos con .isdigit(), .isalpha() y el operador in.
Contenido
- Los dos cabos sueltos, en código
- Conversión explícita:
int(),float(),str() - Qué conversiones funcionan y cuáles fallan: el
ValueError - Conversión implícita, y por qué texto y número no se mezclan
bool()y las reglas de veracidad- Normalizar el texto:
.strip(),.lower()y.capitalize() - Comprobar antes de convertir:
.isdigit()e.isalpha() - Validar contra una lista de valores permitidos
- Qué es validar y por qué se valida siempre
- Un
ifmínimo, y lo que aún nos falta - TareaFácil: sanear y validar la entrada
- Errores comunes y consejos
- Ejercicios
- Conclusión
- Los dos cabos sueltos, en código
Empecemos viendo los dos problemas con toda su crudeza.
Cabo suelto 1: lo que llega de input() es texto.
dias = input("Dias estimados: ") # el usuario teclea 3
print(type(dias)) # <class 'str'>
print(dias + 1) # TypeErrorCabo suelto 2: el texto no se compara como lo escribe una persona.
Y hay más variantes del mismo problema, todas verosímiles:
| Lo que teclea el usuario | prioridad == "alta" |
Por qué |
|---|---|---|
alta |
True |
Coincide exactamente |
ALTA |
False |
Mayúsculas distintas |
Alta |
False |
La primera letra difiere |
alta (con espacio final) |
False |
El espacio es un carácter más |
alta (espacio delante) |
False |
Ídem |
urgente |
False |
No es una prioridad válida |
| (Intro sin escribir nada) | False |
Es la cadena vacía "" |
De las siete filas, solo la primera es lo que queríamos, la última es realmente un error del usuario, y las cinco intermedias son entradas razonables que un programa decente debería aceptar o rechazar con criterio, no por accidente.
Las cinco primeras se arreglan convirtiendo y normalizando; las dos últimas, validando. Vamos con ello.
- Conversión explícita:
int(), float(), str()
int(), float(), str()Python trae funciones que transforman un valor de un tipo a otro. Se llaman igual que el tipo destino y se usan poniendo el valor entre paréntesis.
| Función | Convierte a | Ejemplo | Resultado |
|---|---|---|---|
int(x) |
Entero | int("3") |
3 |
float(x) |
Decimal | float("3.5") |
3.5 |
str(x) |
Texto | str(3.5) |
"3.5" |
bool(x) |
Booleano | bool("") |
False |
A esto se le llama conversión explícita o casting: eres tú quien la pide, en un punto concreto del código.
int(): de texto a entero
Resuelve el primer cabo suelto de golpe:
dias_texto = input("Dias estimados: ") # el usuario teclea 3
dias = int(dias_texto)
print(type(dias)) # <class 'int'>
print(dias + 1) # 4 <- ahora si es aritmetica
print(dias * 2) # 6 <- ya no repite el textoEs habitual escribirlo todo en una línea, envolviendo el input() directamente:
Se lee de dentro afuera: primero se ejecuta input(), que devuelve texto; ese texto se le pasa a int(), que devuelve un entero; y el entero se asigna a dias. Es compacto y muy común, pero tiene un inconveniente que veremos en el apartado 7: si el usuario teclea algo que no es un número, el programa se detiene ahí mismo.
int() también convierte decimales a enteros, y aquí hay que fijarse en un detalle:
print(int(3.9)) # 3 <- NO redondea a 4
print(int(3.1)) # 3
print(int(-3.9)) # -3 <- corta hacia ceroint() trunca, no redondea: elimina la parte decimal sin mirarla. Si quieres redondear al entero más cercano, usa round():
Confundir int() con round() produce errores silenciosos en cálculos de importes y porcentajes. Anótalo.
float(): a decimal
horas = float(input("Horas dedicadas: ")) # el usuario teclea 7.5
print(horas) # 7.5
print(type(horas)) # <class 'float'>float() acepta también texto sin decimales, y lo convierte añadiendo .0:
Pero no acepta la coma decimal española:
Es un problema real con usuarios españoles, que teclean la coma con toda naturalidad. Un programa cuidadoso reemplaza la coma por un punto antes de convertir. Eso se hace con el método .replace(), que pertenece a Cadenas de caracteres; lo menciono para que sepas que existe solución.
str(): a texto
Es la conversión inversa: convierte cualquier valor en su representación textual.
dias = 7
completada = False
fecha = None
print(str(dias)) # 7 (pero ahora es un str)
print(str(completada)) # False
print(str(fecha)) # None
print(type(str(dias))) # <class 'str'>Su uso clásico es concatenar un número con texto usando +, lo que sin conversión daba TypeError:
dias = 7
print("Quedan " + dias + " dias") # TypeError
print("Quedan " + str(dias) + " dias") # Quedan 7 diasDicho esto: en código nuevo casi nunca necesitarás str() para esto, porque las f-strings de la lección anterior hacen la conversión solas y quedan mucho mejor:
str() sigue siendo útil en otros contextos —lo usamos en la ficha de TareaFácil para alinear un booleano como si fuera texto—, pero no como forma habitual de componer mensajes.
- Qué conversiones funcionan y cuáles fallan: el
ValueError
ValueErrorConvertir no siempre es posible. Esta tabla resume lo que puedes esperar:
| Conversión | Resultado | ¿Funciona? |
|---|---|---|
int("3") |
3 |
Sí |
int(" 3 ") |
3 |
Sí — int() ignora los espacios de los extremos |
int("3.0") |
No → ValueError |
|
int("tres") |
No → ValueError |
|
int("") |
No → ValueError |
|
int(3.9) |
3 |
Sí (trunca) |
int(True) |
1 |
Sí (True es 1, False es 0) |
float("3.5") |
3.5 |
Sí |
float("3") |
3.0 |
Sí |
float("3,5") |
No → ValueError |
|
float("abc") |
No → ValueError |
|
str(cualquier cosa) |
Su texto | Siempre funciona |
bool(cualquier cosa) |
True o False |
Siempre funciona |
Hay tres filas que merecen comentario:
int(" 3 ")funciona.int()es tolerante con los espacios de los extremos. No lo es con nada más.int("3.0")falla. Sorprende, porque3.0es un número. Pero paraint(), el texto"3.0"contiene un punto que no pertenece a un entero. Si esperas que el usuario pueda escribir decimales, convierte confloat()y trunca después:int(float("3.0"))da3.str()ybool()nunca fallan. Cualquier valor tiene representación textual y cualquier valor es verdadero o falso. Eso los hace seguros, pero también silenciosos: no te avisarán de un dato absurdo.
Leer un ValueError
Cuando la conversión no puede hacerse, Python lanza un ValueError: el tipo era el correcto (le has pasado un texto a int(), que es lo que espera), pero el valor concreto no sirve.
Traceback (most recent call last):
File "tareafacil.py", line 4, in <module>
dias = int("tres")
^^^^^^^^^^^
ValueError: invalid literal for int() with base 10: 'tres'Un mensaje de error de Python se lee de abajo arriba, y contiene más información de la que parece:
| Parte | Qué te dice |
|---|---|
ValueError |
El tipo de error: el valor no es convertible |
invalid literal for int() |
Estabas intentando convertir a entero |
with base 10 |
En base decimal (la normal) |
'tres' |
El valor exacto que ha fallado |
File "tareafacil.py", line 4 |
El fichero y la línea donde ha ocurrido |
Esa última fila es la más valiosa y la que más gente ignora: el error te dice dónde está el problema. No leas solo la última línea; mira también el número de línea.
Y no confundas los dos errores que aparecen al convertir:
| Error | Significa | Ejemplo |
|---|---|---|
ValueError |
El valor no sirve para esa conversión | int("tres") |
TypeError |
El tipo no admite esa operación | "3" + 4 |
Cuando un ValueError ocurre, el programa se detiene. Todo lo que hubiera después no se ejecuta. Hay dos maneras de evitarlo: comprobar antes de convertir (lo que haremos en el apartado 7) o capturar el error cuando ocurre, con try / except, que se estudia en Depuración y manejo de errores.
- Conversión implícita, y por qué texto y número no se mezclan
Hay conversiones que Python hace solo, sin que se las pidas. Se llaman implícitas, y ocurren únicamente entre tipos numéricos.
Python ha convertido el 3 (un int) a 3.0 (un float) para poder sumarlo con 1.5. La regla es sencilla: cuando se mezclan int y float, el resultado es siempre float, porque es el tipo más «amplio», el que puede representar los valores del otro sin perder información.
print(type(5 + 2)) # <class 'int'>
print(type(5 + 2.0)) # <class 'float'>
print(type(5 / 2)) # <class 'float'> <- recuerda: / siempre da float
print(type(5 // 2)) # <class 'int'>Los booleanos también participan, porque internamente True vale 1 y False vale 0:
Es una curiosidad más que una técnica; no escribas código que dependa de ello.
Por qué Python no convierte texto y número
Y ahora la pregunta que se hace todo el mundo: si Python convierte int a float sin pedírselo, ¿por qué no convierte "3" a 3?
Porque sería ambiguo. "3" + 4 tiene dos interpretaciones igual de razonables:
- Como suma:
3 + 4→7. - Como concatenación:
"3" + "4"→"34".
Python no adivina. Es una decisión de diseño deliberada, y muy acertada: prefiere detenerse y avisarte a hacer algo distinto de lo que querías. Otros lenguajes sí adivinan, y esa comodidad les sale cara. En JavaScript, "3" + 4 da "34" y "3" - 4 da -1: la misma pareja de valores se comporta como texto con un operador y como número con otro, lo cual es una fuente inagotable de errores difíciles de encontrar.
La moraleja práctica: cuando veas un TypeError que menciona str e int, tienes un dato con el tipo equivocado. Casi siempre es un input() sin convertir.
flowchart TD
A["Mezclo dos valores<br/>en una operacion"] --> B{"Ambos son<br/>numericos?"}
B -->|"int y float"| C["Python convierte solo<br/>Resultado: float"]
B -->|"str y numero"| D["TypeError<br/>Convierte tu con int(), float() o str()"]
bool() y las reglas de veracidad
bool() y las reglas de veracidadbool() convierte cualquier valor a True o False, y nunca falla. Pero sus reglas hay que conocerlas, porque no son obvias.
En Python, unos pocos valores se consideran falsos; todos los demás son verdaderos.
| Valor | bool(valor) |
Comentario |
|---|---|---|
0 |
False |
El cero entero |
0.0 |
False |
El cero decimal |
"" |
False |
Cadena vacía |
None |
False |
Ausencia de valor |
False |
False |
Obviamente |
" " |
True |
Un espacio no es vacío: tiene un carácter |
"0" |
True |
Es un texto de un carácter, no el número cero |
"False" |
True |
¡Es un texto! Y no está vacío |
-1 |
True |
Cualquier número distinto de cero |
3.14 |
True |
Ídem |
Las tres filas peligrosas son " ", "0" y "False". Las tres parecen que deberían dar False y las tres dan True, porque son textos no vacíos. bool() sobre un texto solo pregunta una cosa: ¿tiene al menos un carácter?
print(bool("")) # False
print(bool(" ")) # True <- cuidado
print(bool("False")) # True <- cuidado
print(bool("0")) # True <- cuidadoEsto tiene una aplicación directa e importantísima para nosotros: detectar si el usuario pulsó Intro sin escribir nada.
titulo = input("Titulo: ") # el usuario pulsa Intro directamente
hay_titulo = bool(titulo)
print(hay_titulo) # FalseComo input() devuelve "" cuando no se escribe nada, y "" es falso, bool(titulo) nos dice si hay algo. En la práctica se escribe más corto: el propio valor del texto ya sirve como condición, sin envolverlo en bool(). Es lo que Python llama valor de verdad de un objeto.
Pero atención a la trampa que se deriva de la tabla: si el usuario teclea solo espacios, titulo vale " ", que es verdadero. Un título de tres espacios pasaría el filtro. La solución es quitar los espacios antes de comprobar, y de eso trata el apartado siguiente.
Un último aviso: nunca uses bool() para interpretar una respuesta del usuario.
respuesta = input("Completada? (True/False): ") # el usuario teclea False
print(bool(respuesta)) # True <- NO es lo que esperabas"False" es un texto no vacío, así que bool() da True. Para interpretar un sí/no hay que comparar el texto, no convertirlo:
- Normalizar el texto:
.strip(), .lower() y .capitalize()
.strip(), .lower() y .capitalize()Normalizar es transformar un dato a una forma canónica antes de compararlo o guardarlo, de modo que las diferencias irrelevantes —espacios sobrantes, mayúsculas— dejen de importar.
Python ofrece muchos métodos de cadena; los verás todos en Cadenas de caracteres. Aquí nos quedamos con los tres imprescindibles para sanear entradas.
Se llaman con un punto detrás de la variable de texto: variable.metodo().
.strip(): quitar espacios de los extremos
Elimina espacios, tabuladores y saltos de línea al principio y al final, pero no los de dentro:
Es la primera operación que debe sufrir cualquier texto que venga de un teclado. Los espacios sobrantes son invisibles en pantalla y rompen todas las comparaciones.
.lower(): pasar todo a minúsculas
Aquí está, por fin, la solución al cabo suelto de la lección 01-05:
También existe .upper(), que hace lo contrario. La técnica se llama comparación insensible a mayúsculas, y consiste en llevar los dos lados a la misma caja antes de comparar. Como el literal "alta" ya está en minúsculas, basta con bajar el otro.
Lo normal es encadenar las dos operaciones. Los métodos se aplican de izquierda a derecha:
prioridad = input("Prioridad: ") # el usuario teclea " ALTA "
prioridad = prioridad.strip().lower()
print(prioridad) # 'alta'
print(prioridad == "alta") # TrueLéelo así: al texto original se le quitan los espacios, y al resultado se le bajan las mayúsculas. Y fíjate en algo importante de la segunda línea: el resultado se reasigna a la propia variable. Si escribieras solo prioridad.strip().lower() sin el prioridad = delante, el resultado se calcularía y se tiraría, porque los métodos de cadena no modifican el texto original: devuelven uno nuevo. Es la inmutabilidad de las cadenas que viste en la lección 02-01, ahora con consecuencias prácticas.
prioridad = " ALTA "
prioridad.strip().lower() # se calcula y se pierde
print(prioridad) # ' ALTA ' <- no ha cambiado nadaEste es uno de los errores más frecuentes en quien empieza. Recuérdalo: los métodos de cadena devuelven, no modifican.
.capitalize(): primera letra en mayúscula
Útil para nombres propios, que es exactamente el caso del responsable de una tarea:
print("nuria".capitalize()) # Nuria
print("NURIA".capitalize()) # Nuria
print("nUrIa".capitalize()) # NuriaPone la primera letra en mayúscula y el resto en minúscula. Eso último se olvida y a veces sorprende:
Para poner en mayúscula la inicial de cada palabra existe .title(), con sus propias limitaciones. Como el equipo de Estudio Alba son tres nombres de una sola palabra, .capitalize() nos basta.
| Método | Qué hace | " nUrIa " → |
|---|---|---|
.strip() |
Quita espacios de los extremos | "nUrIa" |
.lower() |
Todo a minúsculas | " nuria " |
.upper() |
Todo a mayúsculas | " NURIA " |
.capitalize() |
Primera letra mayúscula, resto minúscula | " nuria " |
.strip().capitalize() |
Encadenados | "Nuria" |
Fíjate en la anteúltima fila: .capitalize() sobre un texto que empieza por espacio no hace nada visible, porque la «primera letra» es un espacio. El .strip() va siempre primero.
- Comprobar antes de convertir:
.isdigit() e .isalpha()
.isdigit() e .isalpha()Ya sabemos que int("tres") detiene el programa. La forma de evitarlo sin recurrir todavía al manejo de errores es preguntar antes si la conversión es posible.
.isdigit() responde a: ¿este texto está compuesto solo por dígitos? Devuelve un booleano.
print("3".isdigit()) # True
print("300".isdigit()) # True
print("tres".isdigit()) # False
print("3.5".isdigit()) # False <- el punto no es un digito
print("-5".isdigit()) # False <- el signo tampoco
print("".isdigit()) # False <- cadena vacia
print(" 3".isdigit()) # False <- el espacio tampocoEs una comprobación estricta, y esa estrictez es una virtud: si .isdigit() devuelve True, int() funcionará con seguridad. Pero ten presentes sus tres límites: no admite decimales, no admite negativos y no tolera espacios. Por eso el .strip() debe ir antes:
entrada = input("Dias estimados: ") # el usuario teclea " 5 "
entrada = entrada.strip()
print(entrada.isdigit()) # True.isalpha() es su equivalente para letras: ¿está compuesto solo por letras?
print("Luis".isalpha()) # True
print("Nuria3".isalpha()) # False
print("Luis Gomez".isalpha()) # False <- el espacio no es una letra
print("".isalpha()) # FalseSirve para detectar que alguien ha escrito un número donde iba un nombre. Ojo con la tercera línea: un nombre compuesto contiene un espacio y no pasa el filtro, así que .isalpha() es adecuado para nombres de una sola palabra, como los del equipo de Estudio Alba, pero no para nombres y apellidos.
| Método | Devuelve True cuando... |
Casos que rechaza |
|---|---|---|
.isdigit() |
Solo hay dígitos y hay al menos uno | "", "3.5", "-5", "3 " |
.isalpha() |
Solo hay letras y hay al menos una | "", "Luis1", "Luis Gomez" |
.isspace() |
Solo hay espacios en blanco | "", cualquier texto con contenido |
El patrón de uso seguro queda así:
1. Leer con input() 2. Limpiar con .strip() (y .lower() si es una opcion de un conjunto) 3. Comprobar con .isdigit() / in (...) / longitud 4. Solo si la comprobacion pasa: convertir con int() o float()
Ese orden no es negociable: limpiar, comprobar, convertir.
- Validar contra una lista de valores permitidos
Cuando un dato solo puede tomar unos pocos valores —como la prioridad de TareaFácil, que según el requisito R2 solo admite alta, media y baja—, la comprobación se escribe con el operador in que aprendiste en la lección 02-02:
prioridad = input("Prioridad (alta/media/baja): ")
prioridad = prioridad.strip().lower()
prioridad_valida = prioridad in ("alta", "media", "baja")
print(prioridad_valida)Con esa normalización previa, todas estas entradas se aceptan correctamente: alta, ALTA, Alta, alta , aLtA. Y estas se rechazan, como debe ser: urgente, altisima, `` (vacío), 1.
Lo mismo para el responsable, que según R3 debe ser una de las tres personas del estudio:
responsable = input("Responsable (Marta/Luis/Nuria): ")
responsable = responsable.strip().capitalize()
responsable_valido = responsable in ("Marta", "Luis", "Nuria")
print(responsable_valido)Fíjate en la diferencia de normalización: para la prioridad usamos .lower(), porque los valores canónicos están en minúscula; para el responsable usamos .capitalize(), porque son nombres propios y queremos guardarlos bien escritos. La normalización debe llevar el dato a la forma en la que lo vas a almacenar, no a una forma cualquiera.
Ventajas de esta forma frente a encadenar comparaciones con or:
- Se lee como la regla de negocio: «la prioridad debe estar entre estas tres».
- Añadir un valor permitido es añadir un elemento, no otra comparación.
- No se puede caer en la trampa de
prioridad == "alta" or "media"que vimos en 02-02.
- Qué es validar y por qué se valida siempre
Recapitulemos con calma, porque este apartado es el corazón conceptual de la lección.
Validar es comprobar que un dato cumple las condiciones necesarias para poder usarse, antes de usarlo. No es una comprobación de tipo: es una comprobación de sentido.
Un dato puede fallar en cuatro niveles distintos, y conviene distinguirlos:
| Nivel | Pregunta | Ejemplo de fallo | Herramienta |
|---|---|---|---|
| Presencia | ¿Hay algo? | Título vacío | .strip() y comprobar si queda algo |
| Tipo | ¿Se puede convertir? | Días = "tres" |
.isdigit(), luego int() |
| Dominio | ¿Está entre los valores permitidos? | Prioridad = "urgente" |
in ("alta", "media", "baja") |
| Coherencia | ¿Tiene sentido? | Días estimados = -5 o 9000 |
Comparaciones: 1 <= dias <= 365 |
Ese cuarto nivel se olvida siempre y es donde se cuelan los datos más absurdos. "-5" no pasaría .isdigit(), pero "0" sí, y una tarea de cero días estimados no significa nada. Convertir bien no basta: hay que preguntarse si el valor tiene sentido en el negocio.
¿Y por qué se valida todo lo que viene de fuera, siempre, sin excepción? Por tres razones de peso creciente:
-
Robustez. Un programa que se detiene con
ValueErrorporque alguien tecleó una letra de más es un programa que nadie querrá usar. Marta no debería tener que llamar a nadie porque el programa «ha petado». -
Integridad de los datos. Si TareaFácil acepta la prioridad
"urgentisima", esa tarea nunca aparecerá al filtrar por prioridad alta, media o baja. El programa no fallará: mentirá, que es mucho peor. Un error visible se arregla; un dato corrupto se propaga durante meses. -
Seguridad. En programas conectados a bases de datos o a internet, los datos no validados son la puerta de entrada de los ataques más comunes del mundo. Está fuera del alcance de este curso, pero el hábito que estás construyendo ahora es exactamente el que evita esos problemas.
Y la regla de oro, que conviene aprenderse literalmente:
Nunca confíes en los datos que vienen de fuera del programa. Ni del teclado, ni de un fichero, ni de la red. Ni siquiera si quien los teclea eres tú.
Esa última coletilla no es retórica. El programador es el peor probador de su propio programa, porque teclea siempre lo que el programa espera. Marta no.
- Un
if mínimo, y lo que aún nos falta
if mínimo, y lo que aún nos faltaHasta ahora hemos calculado booleanos (prioridad_valida, hay_titulo) y los hemos impreso. Pero validar sirve de poco si el programa no hace algo distinto según el resultado.
Para eso está el if, que se estudia a fondo en la próxima lección, Condicionales. Aquí lo usamos en su forma más simple, solo para que la validación tenga efecto visible:
prioridad = input("Prioridad (alta/media/baja): ")
prioridad = prioridad.strip().lower()
if prioridad in ("alta", "media", "baja"):
print(f"Prioridad aceptada: {prioridad}")
else:
print("Prioridad no valida. Se asigna 'media' por defecto.")
prioridad = "media"
print(f"Prioridad final: {prioridad}")Tres apuntes sobre la sintaxis, sin extendernos porque no es el tema de esta lección:
- La línea del
iftermina en dos puntos (:). - Lo que va debajo está indentado (4 espacios), y esa indentación es la que le dice a Python qué instrucciones pertenecen a cada rama. En Python la indentación no es estética: es sintaxis.
elsemarca el camino alternativo, el que se toma cuando la condición es falsa.
Con esto, la validación ya no solo informa: actúa. Rechaza el valor incorrecto y pone uno por defecto.
Aun así, quedan dos limitaciones que este módulo no puede resolver:
No podemos volver a preguntar. Lo natural sería insistir hasta que el usuario escriba algo válido, tal y como diseñamos en el pseudocódigo de la lección 01-05 (aquel MIENTRAS prioridad_valida = FALSO). Repetir requiere un bucle, y los bucles llegan en Bucles; la aplicación completa a un menú de TareaFácil, en Menús interactivos. Por ahora, la estrategia es asignar un valor por defecto y avisar.
No podemos capturar el error de una conversión. Si el usuario teclea "tres" donde va un número, nuestra defensa es comprobar antes con .isdigit(). La otra vía —dejar que la conversión falle y atrapar el ValueError con try / except— es más potente y más habitual en código profesional, y se estudia en Depuración y manejo de errores.
| Estrategia | Cómo funciona | Dónde se estudia |
|---|---|---|
Comprobar antes (.isdigit()) |
Se evita el error preguntando primero | Aquí |
Valor por defecto (if / else) |
Se acepta una alternativa razonable | Aquí, a fondo en 03-01 |
| Volver a preguntar | Se insiste hasta obtener un dato válido | 03-02 y 03-04 |
Capturar el error (try / except) |
Se deja fallar y se gestiona la caída | 08-02 |
Las cuatro son válidas y se combinan. Vas a acabar el curso usando las cuatro.
- TareaFácil: sanear y validar la entrada
Versión 0.5 de tareafacil.py. Retomamos la 0.4 de la lección anterior y añadimos lo aprendido: normalizamos los textos, convertimos los números que ahora sí podemos pedir por teclado, y validamos los cuatro niveles del apartado 9.
# tareafacil.py - Estudio Alba
# Version 0.5: entrada saneada, convertida y validada
NOMBRE_APP = "TareaFacil"
VERSION = "0.5"
ESTUDIO = "Estudio Alba"
ANCHO = 46
HORAS_POR_JORNADA = 8
PRIORIDADES = ("alta", "media", "baja")
EQUIPO = ("Marta", "Luis", "Nuria")
DIAS_POR_DEFECTO = 5
# --- Cabecera ---
print("=" * ANCHO)
print(f"{NOMBRE_APP + ' v' + VERSION:^{ANCHO}}")
print(f"{'Registro de tarea - ' + ESTUDIO:^{ANCHO}}")
print("=" * ANCHO)
print()
# ============================================================
# ENTRADA + SANEADO + VALIDACION
# ============================================================
# --- 1. Titulo: presencia ---
titulo = input("Titulo de la tarea : ")
titulo = titulo.strip()
aviso_titulo = ""
if titulo == "":
titulo = "(sin titulo)"
aviso_titulo = "AVISO: el titulo estaba vacio."
# --- 2. Responsable: dominio ---
responsable = input("Responsable (Marta/Luis/Nuria): ")
responsable = responsable.strip().capitalize()
aviso_responsable = ""
if responsable not in EQUIPO:
aviso_responsable = f"AVISO: '{responsable}' no es del equipo. Se asigna a Marta."
responsable = "Marta"
# --- 3. Prioridad: dominio ---
prioridad = input("Prioridad (alta/media/baja) : ")
prioridad = prioridad.strip().lower()
aviso_prioridad = ""
if prioridad not in PRIORIDADES:
aviso_prioridad = f"AVISO: '{prioridad}' no es una prioridad. Se asigna 'media'."
prioridad = "media"
# --- 4. Dias estimados: tipo + coherencia ---
dias_texto = input("Dias estimados : ")
dias_texto = dias_texto.strip()
aviso_dias = ""
if dias_texto.isdigit():
dias_estimados = int(dias_texto)
if dias_estimados < 1 or dias_estimados > 365:
aviso_dias = f"AVISO: {dias_estimados} dias no es razonable. Se asignan {DIAS_POR_DEFECTO}."
dias_estimados = DIAS_POR_DEFECTO
else:
aviso_dias = f"AVISO: '{dias_texto}' no es un numero. Se asignan {DIAS_POR_DEFECTO} dias."
dias_estimados = DIAS_POR_DEFECTO
# ============================================================
# PROCESO
# ============================================================
horas_estimadas = dias_estimados * HORAS_POR_JORNADA
completada = False
es_urgente = prioridad == "alta" and not completada
# ============================================================
# SALIDA
# ============================================================
print()
print("=" * ANCHO)
print(f"{'FICHA DE TAREA':^{ANCHO}}")
print("=" * ANCHO)
print(f"{'Titulo':<16}{titulo:>{ANCHO - 16}}")
print(f"{'Responsable':<16}{responsable:>{ANCHO - 16}}")
print(f"{'Prioridad':<16}{prioridad:>{ANCHO - 16}}")
print(f"{'Dias estimados':<16}{dias_estimados:>{ANCHO - 16}}")
print(f"{'Horas estimadas':<16}{horas_estimadas:>{ANCHO - 16}}")
print(f"{'Urgente':<16}{str(es_urgente):>{ANCHO - 16}}")
print("=" * ANCHO)
# --- Avisos de validacion ---
print()
print(aviso_titulo)
print(aviso_responsable)
print(aviso_prioridad)
print(aviso_dias)Una ejecución con entradas «sucias» pero razonables, de las que Marta escribiría un lunes con prisa:
==============================================
TareaFacil v0.5
Registro de tarea - Estudio Alba
==============================================
Titulo de la tarea : Manual de marca Sole
Responsable (Marta/Luis/Nuria): LUIS
Prioridad (alta/media/baja) : ALTA
Dias estimados : 4
==============================================
FICHA DE TAREA
==============================================
Titulo Manual de marca Sole
Responsable Luis
Prioridad alta
Dias estimados 4
Horas estimadas 32
Urgente True
==============================================Las cuatro entradas venían con espacios o en mayúsculas, y las cuatro se han aceptado y guardado en su forma canónica. En la versión 0.4, esa misma ejecución habría producido Urgente: False, porque " ALTA " no era "alta". El cabo suelto de la lección 01-05 queda atado.
Y ahora una ejecución con entradas realmente erróneas:
Titulo de la tarea :
Responsable (Marta/Luis/Nuria): Pedro
Prioridad (alta/media/baja) : urgentisima
Dias estimados : cuatro
==============================================
FICHA DE TAREA
==============================================
Titulo (sin titulo)
Responsable Marta
Prioridad media
Dias estimados 5
Horas estimadas 40
Urgente False
==============================================
AVISO: el titulo estaba vacio.
AVISO: 'Pedro' no es del equipo. Se asigna a Marta.
AVISO: 'urgentisima' no es una prioridad. Se asigna 'media'.
AVISO: 'cuatro' no es un numero. Se asignan 5 dias.El programa no se ha detenido en ningún momento. Antes, int("cuatro") habría lanzado un ValueError y la ejecución habría muerto ahí, sin ficha y sin explicación. Ahora cada dato incorrecto se detecta, se sustituye por algo razonable y se informa de lo ocurrido.
Repasemos las decisiones de diseño, porque son tan importantes como el código:
PRIORIDADESyEQUIPOson constantes en mayúsculas. Las listas de valores permitidos no deberían estar escritas dentro de la comprobación: puestas arriba, se ven de un vistazo y se cambian en un sitio.- El orden es siempre el mismo: leer →
.strip()→ normalizar → comprobar → convertir. No conviertas nunca antes de comprobar. - Cada dato usa la normalización que le corresponde:
.lower()para la prioridad (valor de catálogo en minúsculas),.capitalize()para el responsable (nombre propio), solo.strip()para el título (es texto libre; bajarlo a minúsculas lo estropearía). - Los avisos se guardan en variables y se imprimen al final, para que la ficha salga limpia y los problemas queden agrupados. Cuando no hay error, la variable vale
""y se imprime una línea vacía. - Los días se validan en dos niveles: primero que sean un número (
.isdigit()), después que el número tenga sentido (1 <= dias <= 365). Un0habría pasado el primer filtro y no el segundo.
Lo que sigue siendo mejorable
Sé honesto con esta versión, porque el módulo 3 empieza justo aquí:
| Limitación | Se resuelve en |
|---|---|
| Ante un dato incorrecto se pone un valor por defecto en vez de volver a preguntar | Bucles |
| Los cuatro bloques de validación repiten la misma estructura | Definición y uso de funciones |
| Solo se puede registrar una tarea por ejecución | Menús interactivos y Listas |
| Al cerrar el programa, la tarea se pierde | Guardar datos en archivos |
| Los avisos vacíos imprimen líneas en blanco | Condicionales |
Errores Comunes y Consejos
Olvidar convertir lo que devuelve input(). Es el error número uno del módulo. Si vas a calcular, convierte. Si el TypeError menciona str e int, mira tus input().
Convertir antes de comprobar. int(input(...)) es cómodo y frágil: cualquier letra tira el programa. El orden seguro es leer, limpiar, comprobar y solo entonces convertir.
Creer que int() redondea. int(3.9) es 3. Para redondear, round().
Esperar que int("3.0") funcione. No funciona: el punto no pertenece a un entero. Usa int(float("3.0")) si necesitas admitir esa forma.
Olvidar que los métodos de cadena no modifican. titulo.strip() sin asignar no hace nada. Tiene que ser titulo = titulo.strip().
Normalizar solo un lado de la comparación. prioridad.lower() == "Alta" sigue siendo False siempre. Si bajas un lado, el otro debe estar ya en minúsculas.
Aplicar .capitalize() antes de .strip(). " nuria".capitalize() no cambia nada visible, porque la primera «letra» es un espacio. El .strip() va primero.
Confiar en bool() para interpretar respuestas. bool("False") es True, y bool("0") también. Para sí/no, compara el texto normalizado.
Comprobar el vacío sin limpiar antes. titulo == "" no detecta un título de tres espacios. titulo.strip() == "" sí.
Usar .isdigit() con negativos o decimales. "-5" y "3.5" devuelven False. Si tu dato admite decimales o signo, .isdigit() no es la comprobación adecuada.
Validar solo el tipo y olvidar la coherencia. "0" y "99999" son dígitos perfectamente válidos, y ninguno de los dos es un número de días razonable. Pregúntate siempre si el valor tiene sentido en el problema, no solo si es convertible.
Consejo: normaliza en el momento de leer. Deja dato = input(...).strip() justo en la lectura, y a partir de ahí trabaja siempre con el dato limpio. Si la limpieza queda dispersa por el programa, tarde o temprano habrá un camino que se la salte.
Consejo: pon las opciones válidas en constantes. PRIORIDADES = ("alta", "media", "baja") arriba del fichero. La regla de negocio queda visible y se cambia en un solo sitio.
Consejo: prueba tu programa como si quisieras romperlo. Pulsa Intro sin escribir. Teclea espacios. Escribe en mayúsculas. Pon una letra donde va un número. Pon un número negativo. Cada cosa que rompa el programa es una validación que falta.
Ejercicios
Ejercicio 1: Predecir el resultado
Indica el resultado —o el error, con su nombre— de cada expresión, sin ejecutar nada.
a) int("42")
b) int("42.0")
c) int(42.9)
d) round(42.9)
e) float("7")
f) float("7,5")
g) str(True) + " valor"
h) bool("")
i) bool(" ")
j) bool("0")
k) " Alta ".strip().lower()
l) " alta ".lower().strip() == "alta"
m) "LUIS".capitalize()
n) " luis".capitalize()
o) "12".isdigit()
p) "-12".isdigit()
q) "3 dias".isdigit()
r) "Nuria" in ("Marta", "Luis", "Nuria")
s) "nuria" in ("Marta", "Luis", "Nuria")
t) "3" + 4Ejercicio 2: Sanear una entrada de horas
Escribe un programa registrar_horas.py que pida por teclado el nombre de una persona del equipo y las horas dedicadas a una tarea (un número entero), y que:
- Limpie ambas entradas de espacios sobrantes.
- Normalice el nombre a la forma
Nombre(inicial mayúscula, resto minúsculas). - Compruebe que el nombre está en el equipo (Marta, Luis, Nuria); si no, avise y asigne
"Marta". - Compruebe que las horas son un número entero entre 1 y 40; si no, avise y asigne
8. - Calcule y muestre las jornadas completas de 8 horas y las horas sueltas (recuerda
//y%de la lección 02-02). - Muestre una ficha alineada con f-strings.
Prueba tu programa con estas cuatro entradas y comprueba que ninguna lo detiene: NURIA / 12, luego pedro / 8, luego Luis / ocho, y por último Luis / 100.
Ejercicio 3: Validar los cuatro niveles
Para cada uno de estos datos de TareaFácil, indica en qué nivel de validación falla (presencia, tipo, dominio o coherencia), qué herramienta de esta lección lo detecta y qué debería hacer el programa. Después escribe el fragmento de código que lo comprueba.
| Dato | Valor introducido |
|---|---|
| a | Título: " " |
| b | Prioridad: "URGENTE" |
| c | Días estimados: "quince" |
| d | Días estimados: "0" |
| e | Responsable: "nuria" |
| f | Horas dedicadas: "50" sobre 8 horas estimadas |
Soluciones
Solución 1.
| Expresión | Resultado | |
|---|---|---|
| a | int("42") |
42 |
| b | int("42.0") |
ValueError: el punto no pertenece a un entero |
| c | int(42.9) |
42 (trunca, no redondea) |
| d | round(42.9) |
43 |
| e | float("7") |
7.0 |
| f | float("7,5") |
ValueError: la coma no es separador decimal |
| g | str(True) + " valor" |
"True valor" |
| h | bool("") |
False |
| i | bool(" ") |
True — un espacio es un carácter |
| j | bool("0") |
True — es un texto no vacío |
| k | " Alta ".strip().lower() |
"alta" |
| l | " alta ".lower().strip() == "alta" |
True — aquí el orden da igual, pero .strip() primero es el hábito correcto |
| m | "LUIS".capitalize() |
"Luis" |
| n | " luis".capitalize() |
" luis" — la primera «letra» es un espacio, no cambia nada |
| o | "12".isdigit() |
True |
| p | "-12".isdigit() |
False — el signo no es un dígito |
| q | "3 dias".isdigit() |
False |
| r | "Nuria" in ("Marta", "Luis", "Nuria") |
True |
| s | "nuria" in ("Marta", "Luis", "Nuria") |
False — distingue mayúsculas; falta .capitalize() |
| t | "3" + 4 |
TypeError |
Las filas n y s son las que más se fallan, y las dos apuntan a lo mismo: normalizar mal es tan peligroso como no normalizar, porque da falsa sensación de seguridad.
Solución 2.
# registrar_horas.py - Estudio Alba
ANCHO = 40
EQUIPO = ("Marta", "Luis", "Nuria")
HORAS_POR_JORNADA = 8
HORAS_POR_DEFECTO = 8
print("=" * ANCHO)
print(f"{'REGISTRO DE HORAS':^{ANCHO}}")
print("=" * ANCHO)
print()
# --- Nombre: saneado y validacion de dominio ---
nombre = input("Persona (Marta/Luis/Nuria): ")
nombre = nombre.strip().capitalize()
aviso_nombre = ""
if nombre not in EQUIPO:
aviso_nombre = f"AVISO: '{nombre}' no es del equipo. Se asigna a Marta."
nombre = "Marta"
# --- Horas: validacion de tipo y de coherencia ---
horas_texto = input("Horas dedicadas (1-40) : ")
horas_texto = horas_texto.strip()
aviso_horas = ""
if horas_texto.isdigit():
horas = int(horas_texto)
if horas < 1 or horas > 40:
aviso_horas = f"AVISO: {horas} h esta fuera del rango 1-40. Se asignan {HORAS_POR_DEFECTO}."
horas = HORAS_POR_DEFECTO
else:
aviso_horas = f"AVISO: '{horas_texto}' no es un numero. Se asignan {HORAS_POR_DEFECTO} h."
horas = HORAS_POR_DEFECTO
# --- Proceso ---
jornadas = horas // HORAS_POR_JORNADA
horas_sueltas = horas % HORAS_POR_JORNADA
# --- Salida ---
print()
print("-" * ANCHO)
print(f"{'Persona':<18}{nombre:>{ANCHO - 18}}")
print(f"{'Horas dedicadas':<18}{horas:>{ANCHO - 18}}")
print(f"{'Jornadas completas':<18}{jornadas:>{ANCHO - 18}}")
print(f"{'Horas sueltas':<18}{horas_sueltas:>{ANCHO - 18}}")
print("-" * ANCHO)
print(aviso_nombre)
print(aviso_horas)Comportamiento con las cuatro entradas pedidas:
| Entrada | Nombre final | Horas finales | Jornadas | Sueltas | Aviso |
|---|---|---|---|---|---|
NURIA / 12 |
Nuria |
12 | 1 | 4 | Ninguno |
pedro / 8 |
Marta |
8 | 1 | 0 | Pedro no es del equipo |
Luis / ocho |
Luis |
8 | 1 | 0 | 'ocho' no es un número |
Luis / 100 |
Luis |
8 | 1 | 0 | 100 h fuera del rango |
Ninguna de las cuatro detiene el programa. Fíjate en la primera fila: " NURIA " se convierte en "Nuria" gracias a .strip().capitalize(), y por eso pasa la validación de dominio. Sin ese saneado, Nuria —una persona real del equipo— habría sido rechazada.
Solución 3.
| Dato | Nivel | Herramienta | Qué hacer | |
|---|---|---|---|---|
| a | Título " " |
Presencia | titulo.strip() == "" |
Rechazar y pedir de nuevo, o poner "(sin titulo)" |
| b | Prioridad "URGENTE" |
Dominio | .strip().lower() y luego in PRIORIDADES |
Rechazar y asignar "media" por defecto |
| c | Días "quince" |
Tipo | .isdigit() |
No convertir; avisar y usar el valor por defecto |
| d | Días "0" |
Coherencia | Comparación dias >= 1 |
Convertible y numérico, pero sin sentido: rechazar |
| e | Responsable "nuria" |
Ninguno tras normalizar | .strip().capitalize() |
Aceptar: es válido, solo estaba mal escrito |
| f | Horas "50" sobre 8 estimadas |
Coherencia | Comparación entre dos datos | Avisar de desviación grave del presupuesto |
Código de comprobación:
PRIORIDADES = ("alta", "media", "baja")
EQUIPO = ("Marta", "Luis", "Nuria")
# a) Presencia
titulo = " ".strip()
titulo_ok = titulo != ""
print(titulo_ok) # False
# b) Dominio
prioridad = "URGENTE".strip().lower()
prioridad_ok = prioridad in PRIORIDADES
print(prioridad_ok) # False
# c) Tipo
dias_texto = "quince".strip()
dias_ok_tipo = dias_texto.isdigit()
print(dias_ok_tipo) # False
# d) Coherencia
dias_texto = "0".strip()
dias_ok_tipo = dias_texto.isdigit()
print(dias_ok_tipo) # True <- pasa el filtro de tipo
dias = int(dias_texto)
dias_ok_rango = 1 <= dias <= 365
print(dias_ok_rango) # False <- pero no el de coherencia
# e) Dominio, tras normalizar
responsable = "nuria".strip().capitalize()
responsable_ok = responsable in EQUIPO
print(responsable) # Nuria
print(responsable_ok) # True
# f) Coherencia entre dos datos
horas_dedicadas = int("50".strip())
horas_estimadas = 8
desviacion_grave = horas_dedicadas > horas_estimadas * 2
print(desviacion_grave) # TrueLos casos d y e son los que enseñan de verdad. El d demuestra que pasar la validación de tipo no basta: "0" es un dígito perfectamente convertible y una tarea de cero días no significa nada. Y el e demuestra lo contrario: un dato que parecía inválido ("nuria" no está en EQUIPO) resulta ser perfectamente correcto en cuanto se normaliza. Validar sin normalizar antes rechaza datos buenos; normalizar sin validar después acepta datos malos. Hacen falta las dos cosas, y en ese orden.
Conclusión
Con esta lección se cierra el módulo 2, y se cierran también los dos cabos sueltos que lo abrieron.
Sabes convertir con int(), float() y str(), y conoces sus límites: int() trunca en vez de redondear, int("3.0") no funciona, float("3,5") tampoco. Sabes leer un ValueError —que te dice el valor exacto y la línea donde falló— y distinguirlo del TypeError, que aparece cuando el tipo no admite la operación. Entiendes que Python convierta int a float implícitamente pero se niegue a mezclar texto y número, y por qué esa negativa es una virtud del lenguaje y no un capricho.
Conoces las reglas de veracidad de bool() y sus tres trampas (" ", "0" y "False" son verdaderos). Normalizas el texto con .strip(), .lower() y .capitalize(), recordando que los métodos de cadena devuelven un texto nuevo y no modifican el original. Compruebas antes de convertir con .isdigit() e .isalpha(), y validas contra un catálogo de valores permitidos con in. Y tienes un marco mental para no olvidarte de nada: presencia, tipo, dominio y coherencia, en ese orden.
Sobre todo, has interiorizado el principio que sostiene todo lo anterior: nada de lo que viene de fuera del programa es de fiar. Ni del teclado, ni de un fichero, ni de quien lo escribe, aunque seas tú.
TareaFácil está en su versión 0.5 y ha dejado de ser frágil. Marta puede teclear ALTA , LUIS o Manual de marca Sole y el programa lo entiende; puede escribir urgentisima, Pedro o cuatro y el programa no se detiene: avisa, corrige y sigue. La comparación que fallaba en la prueba de escritorio de la lección 01-05 —aquel "ALTA" que no era "alta"— hoy funciona.
Repasa lo conseguido en este módulo: los datos de una tarea viven en variables con el tipo adecuado (02-01), se combinan en expresiones que calculan plazos, avances y urgencias (02-02), entran por teclado y salen en una ficha alineada con f-strings (02-03), y llegan saneados y validados (02-04). Eso es un programa que se usa, no un programa que se lee.
Y sin embargo lo has notado en cada apartado de esta lección: el programa toma decisiones a medias. Pone valores por defecto porque no sabe volver a preguntar. Registra una sola tarea y termina. Los if que hemos usado eran mínimos y prestados de la lección siguiente. Todo eso son estructuras de control: las instrucciones que permiten a un programa elegir caminos y repetir acciones, y que convierten una secuencia lineal en algo que reacciona.
Es justo lo que empieza en el módulo 3, con Condicionales, donde ese if que hemos usado de puntillas se explica a fondo —if, elif, else, condiciones anidadas, indentación— y TareaFácil pasará por fin de avisar de los problemas a decidir qué hacer con ellos.
Fundamentos de la Programación
Módulo 1: Introducción a la Programación
- ¿Qué es la programación?
- Historia de la programación
- Lenguajes de programación
- Entornos de desarrollo
- Del problema al algoritmo
Módulo 2: Conceptos Básicos
- Variables y tipos de datos
- Operadores y expresiones
- Entrada y salida de datos
- Conversión de tipos y validación de datos
Módulo 3: Estructuras de Control
Módulo 4: Funciones y Procedimientos
- Definición y uso de funciones
- Parámetros y retorno de valores
- Ámbito de variables
- Descomponer un programa en funciones
- Funciones como valores: lambda y orden superior
Módulo 5: Estructuras de Datos
- Listas y arreglos
- Cadenas de caracteres
- Diccionarios y conjuntos
- Tuplas y estructuras anidadas
- Guardar datos en archivos: texto, CSV y JSON
Módulo 6: Algoritmos Básicos
Módulo 7: Objetos y Organización del Código
- De los datos a los objetos: clases e instancias
- Atributos, métodos y constructor
- Colecciones de objetos
- Módulos, paquetes e importaciones
Módulo 8: Buenas Prácticas y Herramientas
- Documentación y comentarios
- Depuración y manejo de errores
- Control de versiones
- Pruebas automatizadas
- Estilo, legibilidad y refactorización
