Introducción

snake

¡Bienvenidos al módulo de Programación! Durante este curso exploraremos en profundidad los conceptos fundamentales de la algoritmia y las estructuras de datos, pilares esenciales en el desarrollo de habilidades de pensamiento lógico y resolución de problemas, competencias indispensables para cualquier programador.

A lo largo de las 12 semanas de duración del curso, utilizaremos el lenguaje de programación Python, un lenguaje multipropósito y multiparadigma que destaca por su accesibilidad y su enfoque amigable para el aprendizaje.

Cada semana se incorporarán nuevos contenidos siguiendo la cronología definida para la asignatura. Es imprescindible que el alumnado realice un estudio autónomo de estos contenidos desde casa. Este enfoque permitirá que las clases se centren en resolver dudas y consolidar conocimientos mediante ejercicios prácticos directamente relacionados con los temas trabajados.

Para el estudio autónomo, se recomienda:

  • Consultar los apuntes proporcionados.
  • Investigar en la documentación oficial de Python.
  • Aprovechar recursos adicionales como videotutoriales y cursos disponibles en la sección de recursos de este libro.

La estructura del curso será la siguiente:

  1. Primera parte (8 semanas): Nos enfocaremos en el estudio de algoritmia y estructuras de datos mediante prácticas semanales que facilitarán una comprensión progresiva y sólida de los conceptos clave.
  2. Últimas 4 semanas: Realizaremos un repaso general de todos los contenidos, resolveremos dudas acumuladas y nos prepararemos para el examen final del módulo, programado para la semana 12.

Recursos del curso

A lo largo del curso, utilizaremos los siguientes recursos para consolidar tanto la teoría como la práctica:

Estos recursos les permitirán aprender, practicar y aplicar los conocimientos adquiridos a lo largo del curso, ayudándoles a fortalecer sus habilidades en programación. ¡Empecemos!

Cronología Semipresencial (12 semanas)

  1. SEMANA 0 (Antes de empezar el módulo)

  2. SEMANA 1 (25 Noviembre 2024)

    • TEMA

      1. Básico. Asignación, Constantes, Funciones, comentarios y docstrings.
      2. Aritmética. (Int, Float, Complex, Decimal) y Operaciones (+, -, /, *, // y %)
      3. Booleanos y lógica (True, False, and, or, not)
      4. Condicionales (if, elif, else, truthy, falsy) y Comparaciones (>, <, ==, >=, <=, !=, is, is not, in, not in)
    • LECTURAS

  3. SEMANA 2 (2 Diciembre 2024)

    • TEMA

      1. Cadenas de texto. (Inmutables) - Metódos: for índex, ítem in enumerate(str), split, join, concat, upper, lower, title, capitalize, strip, lstrip, rstrip, replace, find, rfind, index, rindex, count, startswith, endswith, isalnum, isalpha, isdigit, islower, isupper, istitle, isspace, isprintable, isidentifier, isascii, encode, decode, format, f-string
      2. Listas, Tuplas y métodos. (append, insert, remove, reverse, sort, copy, clear, pop, index, count)
      3. Bucles (for, while, range, enumerate, zip, break, continue, pass)
    • LECTURAS

  4. SEMANA 3 (9 Diciembre 2024)

    • TEMA

      1. Diccionarios y métodos. (keys, values, items, get, setdefault, pop, popitem, clear, copy, update)
      2. Conjuntos y métodos. (add, remove, discard, clear, copy, pop, union, intersection, difference, symmetric_difference, isdisjoint, issubset, issuperset)
      3. Unpacking (list, tuple, dict, set)
    • LECTURAS

  5. SEMANA 4 (16 Diciembre 2024)

    • TEMA

      1. Funciones (def, return, lambda, args, kwargs, docstrings, anotaciones, recursividad, closures, decorators)
      2. Generadores y expresiones generadoras. (yield)
    • LECTURAS

  6. SEMANA 5 (13 Enero 2025)

    • TEMA

      1. Programación orientada a objetos. (Clases, Objetos, Métodos, Atributos, Herencia, Polimorfismo, Encapsulamiento)
    • LECTURAS

  7. SEMANA 6 (20 Enero 2025)

  8. SEMANA 7 (27 Enero 2025)

  9. SEMANA 8 (3 Febrero 2025)

  10. SEMANA 9 (10 Febrero 2025)

    • TEMA

      1. Conexiones con Bases de Datos
    • LECTURAS

Prepara tu entorno de desarrollo

Como mínimo necesitaremos en nuestro sistema operativo el intérprete de Python y un editor de texto o IDE.

Instalación de Python

Ir a la página oficial de Python y descargar la última versión estable para tu sistema operativo.

Instalación de un editor de texto

Haremos uso de Visual Studio Code, pero existen otras opciones como Zed, Sublime Text, Atom, PyCharm, etc.

Ir a la página oficial de Visual Studio Code y descargar la última versión estable para tu sistema operativo.

Ejercicios

Esto es como un gimnasio, si no practicas no avanzas. El libro de Aprende Python tiene ejercicios al final de cada capítulo. Para los ejercicios de este libro, se deberá instalar la herramienta pypas. Es altamente recomendable hacer los ejercicios propuestos en el libro. Para instalar pypas deben proceder con los pasos detallados en su documentación. Les he dejado un vídeo para que vean cómo se instala y se usa.

Recursos

El ABC de Python

El objetivo de esta sección en el libro es simple ir directamente al grano.

grano

Partes de un programa Python

Un programa Python tiene un punto de entrada que habitualmente es un fichero main.py. El intérprete de python lee el código linea a linea.

El siguiente código muestra la estructura básica del fichero main.py

# 1. IMPORTACIONES
# Las importaciones deben estar al principio del archivo y seguir el orden recomendado por PEP 8.
# (1): Librerías estándar (si las hay)
# (2): Librerías de terceros (si las hay)
# (3): Librerías locales (si las hay)

import os  # Módulo para interactuar con el sistema operativo
import sys  # Módulo para interactuar con el intérprete de Python

# 2. CONSTANTES
# Las constantes se escriben en mayúsculas con palabras separadas por guiones bajos.
# Deben ser sustantivos

PI = 3.14159  # Valor de PI

# 3. VARIABLES GLOBALES
# Las variables globales se definen fuera de cualquier función.
# Deben ser sustantivos

nombre_usuario = "Ana"  # Nombre del usuario
radio_circulo = 5  # Radio del círculo para el cálculo de área

# 4. DECLARACIÓN DE FUNCIONES
# Las funciones se deben declarar a continuación.
# Siguen la convención de usar snake_case para los nombres de funciones y deben ser verbos.

def saludar(nombre):
    """Función que saluda al usuario por su nombre"""
    return f"Hola, {nombre}!"

def calcular_area_radio(radio):
    """Función para calcular el área de un círculo"""
    import math  # Importación local de math para evitar dependencias globales
    return math.pi * radio ** 2

# 5. ENTRADA PRINCIPAL DEL PROGRAMA CON `if __name__ == "__main__":`
# Asegura que el código se ejecute solo si el archivo es ejecutado directamente, no cuando se importa como módulo.

if __name__ == "__main__":
    # Código principal del programa: se ejecuta cuando el script es ejecutado directamente.

    print(saludar(nombre_usuario))  # Imprime: "Hola, Ana!"

    # Calcular y mostrar el área del círculo
    area = calcular_area_radio(radio_circulo)
    print(f"El área del círculo con radio {radio_circulo} es: {area:.2f}")

    # Mostrar valor de la constante PI
    print(f"El valor de PI es: {PI}")

Ejecución

Para ejecutar este programa, bastaría con abrir una terminal y poner el siguiente comando:

python3 main.py

Siendo python3 el intérprete que vamos a emplear y el segundo valor la ruta absoluta o relativa al script main.py. main.py podría tener cualquier nombre, pero por convenio el programa principal se llama main.

El resultado de ejecutar ese programa es el siguiente:

Hola, Ana!
El área del círculo con radio 5 es: 78.54
El valor de PI es: 3.14159

Tipos primitivos (Inmutables)

Aqui tenemos los tipos de datos más básicos de existentes en Python.

# Esto es un comentario inline
valor_nulo = None
numero = 42
decimal = 3.14
complejo = (5.8 + 3j)
texto = "Hola, mundo"
es_verdadero = True
datos_binarios = b"Hola"

Operadores

Aritméticos

1 + 1   # => 2
8 - 1   # => 7
10 * 2  # => 20
35 / 5  # => 7.0 # El resultado de la division siempre es un float

# división entera
5 // 3       # => 1
-5 // 3      # => -2
5.0 // 3.0   # => 1.0  # funciona tambien para coma flotante (floats)
-5.0 // 3.0  # => -2.0

# módulo o resto de la división
7 % 3   # => 1
-7 % 3  # => 2

# potencia
2 ** 3  # => 8

# prioridad con los paréntesis
1 + 3 * 2       # => 7
(1 + 3) * 2     # => 8

Lógicos

# Importante la mayuscula inicial.
True    # => Verdad
False   # => Falso

not True    # => Falso
not False   # => Verdad

True and False  # => False
False or True   # => True

# True equivale a 1 y False a 0
True + True  # => 2
True * 8     # => 8
False - 5    # => -5

# Comparaciones
0 == False   # => True
2 > True     # => True
2 == True    # => False
-5 != False  # => True

# None, 0 y valores vacios de strings/lists/dicts/tuples/sets evalúan a False.
# El resto a True
bool(0)      # => False
bool("")     # => False
bool([])     # => False
bool({})     # => False
bool(())     # => False
bool(set())  # => False
bool(4)      # => True
bool(-6)     # => True

# Conversiones de tipo y operadores logicos
bool(0)   # => False
bool(2)   # => True
0 and 2   # => 0
bool(-5)  # => True
bool(2)   # => True
-5 or 0   # => -5

# Igualdad es ==
1 == 1  # => True
2 == 1  # => False

# Diferente es !=
1 != 1  # => False
2 != 1  # => True

# Otros comparadores
1 < 10  # => True (menor estricto a)
1 > 10  # => False (mayor estricto a)
2 <= 2  # => True (menor o igual a)
2 >= 2  # => True (mayor o igual a)

# Conocer si un valor está dentro de un rango
1 < 2 and 2 < 3  # => True
2 < 3 and 3 < 2  # => False

# Pero se ve más claro si hacemos esto
1 < 2 < 3  # => True
2 < 3 < 2  # => False

# is es diferente a ==
a = [1, 2, 3, 4]    # se almacena en la variable a la lista de valores 1, 2, 3, 4.
b = a               # b apunta a a
b is a              # => True, a y b apuntan al mismo objeto
b == a              # => True, los objetos a y b son iguales en valor
b = [1, 2, 3, 4]    # se almacena en la variable b la lista de valores 1, 2, 3, 4.
b is a              # => False, a y b no apuntan al mismo objeto
b == a              # => True, los objetos a y b son iguales en valor

Operaciones sobre cadenas de texto

También se pueden realizar operaciones con cadenas de texto, aqui se explican algunas.

# cadenas de texto (str)
"Esto es una cadena de texto"
'Esto tambien'

# concatenar cadenas de texto
"Hola " + "mundo!"
"Hola " "mundo!" # Tambien es posible concatenar sin el operador +

# Acceso a caracter de una cadena (str)
"Hola mundo!"[0]        # => 'H'
"Hola mundo!"[2]        # => 'l'
"Hola mundo!"[-1]       # => '!'
"Hola mundo!"[-2]       # => 'o'

# Longitud de una cadena de texto
len("Hola mundo!")      # => 11

# f-strings
nombre = "Howl"
f"El corazón de {nombre} lo tenía Calcifer" # => El corazón de Howl lo tenía Calcifer"
f"{nombre} tiene {len(nombre)} caracteres"  # => "Howl tiene 4 caracteres"

# None también es un objeto
None
type(None)  # => <class 'NoneType'> objecto de tipo NoneType

# No usar el operador == para comparar objetos a None. Usemos is
"etc" is None   # => False
None is None    # => True

Variables

Variables

Una variable es un espacio dónde almacenamos los valores de los datos con los que trabajamos. Podemos verlo como una caja en la que podemos guardar cosas. Esas cosas en python son Objetos (veremos este concepto en profundidad en Clases).

# No hay necesidad de declarar las variables antes de asignarlas.
una_variable = 5    # La convención es usar guiones_bajos_con_minúsculas
una_variable #=> 5

# Acceder a variables no asignadas previamente es una excepción.
# Ve Control de Flujo para aprender más sobre el manejo de excepciones.
otra_variable  # Levanta un error de nombre

Colecciones

Podemos agrupar los tipos primitivos vistos con anterioridad en colecciones. Las colecciones son estructuras que permiten almacenar varios Objetos de python

Listas (Mutable)

Almacena una secuencia de elementos y mantiene su orden. Las listas pueden crecer y decrecer

# Listas almacena secuencias
lista = [] # una lista vacía
# Puedes empezar una lista con los valores que desees
otra_lista = [4, 5, 6]

# Añadir elementos al final de una lista con 'append'
lista.append(1)    #lista ahora es [1]
lista.append(2)    #lista ahora es [1, 2]
lista.append(4)    #lista ahora es [1, 2, 4]
lista.append(3)    #lista ahora es [1, 2, 4, 3]
# Retirar del final de la lista con 'pop'
lista.pop()        #=> 3 y lista ahora es [1, 2, 4]
# Pongámoslo de vuelta
lista.append(3)    # Nuevamente lista ahora es [1, 2, 4, 3].

# Acceder a un elemento según su posición en la lista
lista[0] #=> 1
# Acceder al último elemento
lista[-1] #=> 3

# Acceder fuera del rango de la lista lanza un error 'IndexError'
lista[4] # Levanta la excepción IndexError

# Puedes mirar por rango con la sintáxis de trozo (slice)
# (Se le conoce como rango [cerrado/abierto) para los matemáticos.)
lista[1:3] #=> [2, 4]
# Omite el inicio
lista[2:] #=> [4, 3]
# Omite el final
lista[:3] #=> [1, 2, 4]
# Selecciona cada dos elementos
lista[::2]   # =>[1, 4]
# Invierte la lista
lista[::-1]   # => [3, 4, 2, 1]
# Usa cualquier combinación de estos para crear trozos avanzados
# lista[inicio:final:pasos]

# Remueve elementos arbitrarios de una lista con 'del'
del lista[2] # lista ahora es [1, 2, 3]

# Puedes sumar listas
lista + otra_lista #=> [1, 2, 3, 4, 5, 6] - Nota: lista y otra_lista no se tocan

# Concatenar listas con 'extend'
lista.extend(otra_lista) # lista ahora es [1, 2, 3, 4, 5, 6]

# Verifica la existencia de un elemento en una lista con 'in'
1 in lista #=> True

# Examina el largo de una lista con 'len'
len(lista) #=> 6

Tuplas (Inmutable)

Las tuplas equivalentes a las listas pero inmutables. No es posible modificar sus valores ni tamaño una vez declaradas.

tupla = (1, 2, 3)
tupla[0] #=> 1
tupla[0] = 3  # Levanta un error TypeError

# También puedes hacer todas esas cosas que haces con listas
len(tupla) #=> 3
tupla + (4, 5, 6) #=> (1, 2, 3, 4, 5, 6)
tupla[:2] #=> (1, 2)
2 in tupla #=> True

# Puedes desempacar tuplas (o listas) en variables
a, b, c = (1, 2, 3)     # a ahora es 1, b ahora es 2 y c ahora es 3
# Tuplas son creadas por defecto si omites los paréntesis
d, e, f = 4, 5, 6
# Ahora mira que fácil es intercambiar dos valores
e, d = d, e     # d ahora es 5 y e ahora es 4

Conjuntos (Mutables)

Los conjuntos es una colección de valores, al igual que las listas, pero sin orden y donde no es posible que existan valores repetidos.

# Sets (conjuntos)
conjunto_vacio = set()
# Inicializar un conjunto con montón de valores.
un_conjunto = {1,2,2,3,4} # un_conjunto ahora es {1, 2, 3, 4}

# Añade más valores a un conjunto
conjunto_lleno.add(5) # conjunto_lleno ahora es {1, 2, 3, 4, 5}
conjunto_lleno.add(5) # conjunto_lleno sigue siendo {1, 2, 3, 4, 5} aunque agregue otro 5

# Haz intersección de conjuntos con &
otro_conjunto = {3, 4, 5, 6}
conjunto_lleno & otro_conjunto #=> {3, 4, 5}

# Haz unión de conjuntos con |
conjunto_lleno | otro_conjunto #=> {1, 2, 3, 4, 5, 6}

# Haz diferencia de conjuntos con -
{1,2,3,4} - {2,3,5} #=> {1, 4}

# Verifica la existencia en un conjunto con 'in'
2 in conjunto_lleno #=> True
10 in conjunto_lleno #=> False

Diccionarios (Mutables)

Los diccionarios relacionan llaves y valores. Ahora no accedemos al elemento por su posición como hacíamos en una lista, sino por su clave.

dicc_vacio = {}
dicc_vacio = dict() # Es posible declararlo de esta forma
# Aquí está un diccionario prellenado
dicc_lleno = {"uno": 1, "dos": 2, "tres": 3}
dicc_lleno = dict(uno=1, dos=2, tres=3) # o también de esta forma

# Busca valores con []
dicc_lleno["uno"] #=> 1 # la clave (key) es la cadena de texto "uno" y su valor es el int 1

# Obtener todas las llaves como una lista con 'keys()'. Necesitamos envolver la llamada en 'list()' porque obtenemos un iterable. Hablaremos de eso luego.
list(dicc_lleno.keys()) #=> ["tres", "dos", "uno"]
# Nota - El orden de las llaves del diccionario no está garantizada.
# Tus resultados podrían no ser los mismos del ejemplo.

# Obtén todos los valores como una lista. Nuevamente necesitamos envolverlas en una lista para sacarlas del iterable.
list(dicc_lleno.values()) #=> [3, 2, 1]
# Nota - Al igual que con las llaves, no se garantiza el orden.

# Verifica la existencia de una llave en el diccionario con 'in'
"uno" in dicc_lleno #=> True
1 in dicc_lleno #=> False

# Buscar una llave inexistente deriva en KeyError
dicc_lleno["cuatro"] # KeyError

# Usa el método 'get' para evitar la excepción KeyError
dicc_lleno.get("uno") #=> 1
dicc_lleno.get("cuatro") #=> None
# El método 'get' soporta un argumento por defecto cuando el valor no existe.
dicc_lleno.get("uno", 4) #=> 1
dicc_lleno.get("cuatro", 4) #=> 4

# El método 'setdefault' inserta en un diccionario solo si la llave no está presente
dicc_lleno.setdefault("cinco", 5) #dicc_lleno["cinco"] es puesto con valor 5
dicc_lleno.setdefault("cinco", 6) #dicc_lleno["cinco"] todavía es 5

# Elimina llaves de un diccionario con 'del'
del dicc_lleno['uno'] # Remueve la llave 'uno' de dicc_lleno

Flujos de datos estándar

En sistemas UNIX, los programas básicamente manejan tres flujos de datos estándar:

  • Entrada estándar (stdin) -> Permite recibir datos del usuario o de otro programa.
  • Salida estándar (stdout) -> Se emplea para usar resultados correctos del programa.
  • Salida de errores (stderr) -> Se usa para mostrar mensajes de error sin mezclarlos con la salida estándar.

Python hace uso de estos tres flujos, y podemos acceder a ellos en sys.stdin, sys.stdout y sys.stderr. Sin embargo, no haremos uso de ellos de forma directa, sino que emplearemos funciones específicas básicas como print, input para interactuar con el usuario. Y el módulo logging para depurar nuestro código.

Salida estándar

El método print() envía datos a la salida estándar, la cual generalmente es la terminal. Internamente hace uso de sys.stdin.

print("Hola, mundo!")  # Salida estándar normal

Entrada estándar

Se usa input() para recibir datos del usuario desde la terminal.

nombre = input("Introduce tu nombre: ")
print(f"Hola, {nombre}!")

Errores y depuración

Nuestros programas pueden generar errores, por lo que es fundamental que no se mezclen con la salida estándar. Para evitarlo, las excepciones (Exceptions), que interrumpen el flujo normal de ejecución del programa, envían sus mensajes a la salida de errores sys.stderr. En la sección Control de flujo e Iterables veremos cómo gestionarlas correctamente.

Es una buena práctica desarrollar software trazable, es decir, que proporcione información detallada sobre su funcionamiento en cada momento. Esto facilita la monitorización y depuración en cualquier entorno.

Para ello, es recomendable llevar un registro de eventos o logs. Estos registros también se envían a sys.stderr. En lugar de escribir directamente en esta salida, utilizamos el módulo logging, que nos ofrece distintos niveles de registro:

  • DEBUG: Mensajes de depuración
  • INFO: Información general
  • WARNING: Advertencias
  • ERROR: Errores no fatales
  • CRITICAL: Errores graves

El módulo logging es la mejor opción para registrar eventos y depurar el programa, ya que es más flexible y organizado. Usar print para esto no es recomendable.

Mientras que en el entorno de producción se suele hacer uso de los niveles WARNING, ERROR y CRITICAL; en desarrollo se usan todos los niveles para obtener detalles sobre el funcionamiento del programa y detectar problemas.

Ejemplo de uso:

import logging

# Configuración básica del log
logging.basicConfig(level=logging.DEBUG, format='%(levelname)s: %(message)s')

logging.debug("Mensaje de depuración")  # Solo visible si el nivel es DEBUG
logging.info("Mensaje informativo")
logging.warning("Advertencia")
logging.error("Error detectado")
logging.critical("Error crítico")

Control de flujo e Iterables

Por simplificar muchísimo tenemos fundamentalmente condicionales y bucles.

Condicionales

Con los condicionales (if-else, if-elif-else) podemos ejecutar o no una parte del código dependiendo de si se cumple una condición que evalúa a un booleano (True o False).

# Creemos una variable para experimentar
una_variable = 5

# Aquí está una declaración de un 'if'. ¡La indentación es significativa en Python!
# imprime "una_variable es menor que 10"
if una_variable > 10:
    print("una_variable es mayor que 10.")
elif una_variable < 10: # Esta condición 'elif' es opcional.
    print("una_variable es menor que 10")
else: # También es opcional.
    print("una_variable es de hecho 10.")

Bucles

Con los bucles (for, while) podemos repetir una sección del código un número concreto de veces (for) o mientras se cumpla una condición que evalúe a True(while).

For

Itera sobre iterables como lo son las colecciones (listas, tuplas, cadenas, diccionarios) o los generadores. Sabemos con antelación el número de repeticiones en las que vamos a iterar.

"""
imprime:
    perro es un mamifero
    gato es un mamifero
    raton es un mamifero
"""
for animal in ["perro", "gato", "raton"]:
    print("{} es un mamifero".format(animal))

"""
`range(número)` retorna un generador de números
desde cero hasta el número dado
imprime:
    0
    1
    2
    3
"""
for i in range(4): # range es una función de python que retorna una lista de elementos
    print(i)

While

Itera mientras se cumpla una condición, o hasta que la condición deje de cumplirse.

"""
imprime:
    0
    1
    2
    3
"""
x = 0
while x < 4:
    print(x)
    x += 1  # equivalente a x = x + 1

Manejo de excepciones

En cualquier software que hagamos podrían aparecer errores. Esos errores "rompen" el flujo habitual de nuestro programa, lanzan Excepciones, las cuales tendremos que gestionar con los bloques try-except. Para levantar una excepción, hacemos uso de la palabra reservada raise. Veamos un ejemplo.

# Maneja excepciones con un bloque try/except
try:
    # Usa raise para levantar un error
    raise IndexError("Este es un error de indice")
except IndexError as e:
    pass    # Pass no hace nada. Usualmente harias alguna recuperacion aqui.

Iterables

Python oferce una abstracción fundamental llamada Iterable. Un iterable es un objeto que puede ser tratado como una sequencia. El objeto es retornado por la función 'range' es un iterable.

dicc_lleno = {"uno": 1, "dos": 2, "tres": 3}
nuestro_iterable = dicc_lleno.keys()
print(nuestro_iterable) #=> dict_keys(['uno', 'dos', 'tres']). Este es un objeto que implementa nuestra interfaz Iterable

# Podemos recorrerla.
for i in nuestro_iterable:
    print(i)    # Imprime uno, dos, tres

# Aunque no podemos selecionar un elemento por su índice.
nuestro_iterable[1]  # Genera un TypeError

# Un iterable es un objeto que sabe como crear un iterador.
nuestro_iterator = iter(nuestro_iterable)

# Nuestro iterador es un objeto que puede recordar el estado mientras lo recorremos.
# Obtenemos el siguiente objeto llamando la función __next__.
nuestro_iterator.__next__()  #=> "uno"

# Mantiene el estado mientras llamamos __next__.
nuestro_iterator.__next__()  #=> "dos"
nuestro_iterator.__next__()  #=> "tres"

# Después que el iterador ha retornado todos sus datos, da una excepción StopIterator.
nuestro_iterator.__next__() # Genera StopIteration

# Puedes obtener todos los elementos de un iterador llamando a list() en el.
list(dicc_lleno.keys())  #=> Retorna ["uno", "dos", "tres"]

Funciones

A medida que nuestro código fuente crece, surge la necesidad de organizarlo. Las funciones nos ayudan en la organización.

# Usa 'def' para crear nuevas funciones
def add(x, y):
    print("x es {} y y es {}".format(x, y))
    return x + y    # Retorna valores con una la declaración return

# Llamando funciones con parámetros
add(5, 6) #=> imprime "x es 5 y y es 6" y retorna 11

# Otra forma de llamar funciones es con argumentos de palabras claves
add(y=6, x=5)   # Argumentos de palabra clave pueden ir en cualquier orden.

En el ejemplo anterior creamos una función add que recibe los argumentos a y b. Sin embargo, podemos recibir un número variable de argumentos (a, b, c...)

# Puedes definir funciones que tomen un número variable de argumentos
def varargs(*args):
    return args

varargs(1, 2, 3) #=> (1,2,3)


# Puedes definir funciones que toman un número variable de argumentos
# de palabras claves
def keyword_args(**kwargs):
    return kwargs

# Llamémosla para ver que sucede
keyword_args(pie="grande", lago="ness") #=> {"pie": "grande", "lago": "ness"}


# Puedes hacer ambas a la vez si quieres
def todos_los_argumentos(*args, **kwargs): # *args son argumentos posicionales en los que el orden importa
    print(args)
    print(kwargs)
"""
todos_los_argumentos(1, 2, a=3, b=4) imprime:
    (1, 2)
    {"a": 3, "b": 4}
"""

# ¡Cuando llames funciones, puedes hacer lo opuesto a varargs/kwargs!
# Usa * para expandir tuplas y usa ** para expandir argumentos de palabras claves.
args = (1, 2, 3, 4)
kwargs = {"a": 3, "b": 4}
todos_los_argumentos(*args) # es equivalente a foo(1, 2, 3, 4)
todos_los_argumentos(**kwargs) # es equivalente a foo(a=3, b=4)
todos_los_argumentos(*args, **kwargs) # es equivalente a foo(1, 2, 3, 4, a=3, b=4)

Lambdas

También conocidas como funciones anónimas porque no tienen un nombre.

  1. Se escribe en una única sentencia (línea).
  2. No tiene nombre (anónima).
  3. Su cuerpo conlleva un return implícito.
  4. Puede recibir cualquier número de parámetros.
(lambda x: x > 0)(3) #=> True

La función anterior es equivalente a:

def is_positivo(x):
    return x > 0

De hecho, como las lambdas son funciones, podemos asignarlas a variables y usarlas como cualquier otra función.

is_positivo = lambda x: x > 0
is_positivo(-1) #=> False

Generadores

Es una forma sencilla de crear iteradores. Es una función que usando la palabra clave yield retorna un valor y conserva su estado, permitiendo que la función continúe donde se quedó.

def contador(max):
    i = 0
    while i < max:
        yield i
        i += 1

for i in contador(5):
    print(i)

Closures

En Python todo es un Objeto, ¡las funciones también!

# Python tiene funciones de primera clase
def crear_suma(x):
    def suma(y):
        return x + y
    return suma

sumar_10 = crear_suma(10) # Podemos guardar en una variable una función definida por nosotros.
sumar_10(3) #=> 13

# Podemos usar listas por comprensión para mapeos y filtros
[sumar_10(i) for i in [1, 2, 3]]  #=> [11, 12, 13]
[x for x in [3, 4, 5, 6, 7] if x > 5] #=> [6, 7]
# también hay diccionarios
{k:k**2 for k in range(3)} #=> {0: 0, 1: 1, 2: 4}
# y conjuntos por comprensión
{c for c in "la cadena"} #=> {'d', 'l', 'a', 'n', ' ', 'c', 'e'}

Funciones de orden superior

Se conocen en inglés como higher-order functions y son funciones que reciben otras funciones como argumentos. En Python, todo es un objeto, incluidas las funciones. Esto significa que, al igual que podemos asignar una función a una variable, también podemos pasar funciones como argumentos a otras funciones. Usando esta estrategia podríamos explotar el paradigma de la programación funcional con python. En concreto, python nos provee de algunas funciones de orden superior como lo son map y filter.

def make_multiplier_of(n):
    def multiplier(x):
        return x * n
    return multiplier # factoría de funciones

m3 = make_multiplier_of(3)
list(map(m3, [1,2,3])) #=> [3, 6, 9]
list(filter(lambda x: x > 5, [3, 4, 5, 6, 7])) #=> [6, 7]

Recursividad

Una función puede llamarse a sí misma. Aquí tienes un ejemplo de cómo se vería una función recursiva que calcula el factorial de un número:

def factorial(x):
    if x == 1: # Esto es el caso base
        return 1
    else:
        return x * factorial(x - 1)

factorial(5) #=> 120

En las funciones recursivas, es importante tener un caso base que detenga la recursión. En caso contrario, la función se llamará a sí misma infinitamente y Python lanzará un error.

Clases

Los Objetos son instancias concretas del molde de una Clase.

class Humano:

    # Un atributo de clase es compartido por todas las instancias de esta clase
    especie = "H. sapiens"

    # Constructor basico
    def __init__(self, nombre):
        # Asigna el argumento al atributo nombre de la instancia
        self.nombre = nombre

    # Un metodo de instancia. Todos los metodos toman self como primer argumento
    def decir(self, msg):
       return f"{self.nombre}: {msg}"

    # Un metodo de clase es compartido a través de todas las instancias
    # Son llamados con la clase como primer argumento
    @classmethod # decorador
    def get_especie(cls):
        return cls.especie

    # Un metodo estatico es llamado sin la clase o instancia como referencia
    @staticmethod # decorador
    def roncar():
        return "*roncar*"


# Instancia una clase
i = Humano(nombre="Ian")
print(i.decir("hi"))     # imprime "Ian: hi"

j = Humano("Joel")
print(j.decir("hello"))  #imprime "Joel: hello"

# Llama nuestro método de clase
i.get_especie() #=> "H. sapiens"

# Cambia los atributos compartidos
Humano.especie = "H. neanderthalensis"
i.get_especie() #=> "H. neanderthalensis"
j.get_especie() #=> "H. neanderthalensis"

# Llama al método estático
Humano.roncar() #=> "*roncar*"

Persistencia

La persistencia de datos es la capacidad de almacenar información de manera permanente en un sistema de almacenamiento, como un disco duro o una base de datos. Difiere de la memoria volátil, que se pierde al apagar el sistema.

Ficheros

Para guardar de forma permanente los objetos en Python o almacenar los resultados de un proceso, se pueden usar archivos. Estos pueden ser de texto plano, como CSV; binarios, si se serializan los objetos directamente; o con formatos específicos, como hojas de cálculo de Excel.

# Apertura de archivos con open()
# Sintaxis: open("archivo.txt", "modo")

# Modos de apertura:
# "r"  -> Lectura (error si no existe)
# "r+" -> Lectura y escritura (error si no existe)
# "w"  -> Escritura (crea o sobrescribe)
# "w+" -> Escritura y lectura (crea o sobrescribe)
# "a"  -> Añadir (crea si no existe)
# "a+" -> Añadir y leer (crea si no existe)
# "x"  -> Creación exclusiva (error si existe)
# "b"  -> Modo binario (ej: "rb", "wb")
# "t"  -> Texto (por defecto, ej: "rt")

# Ejemplo de escritura en un archivo
txt = open("archivo.txt", "w")  # Abre en modo escritura
txt.write("Hola, mundo!\n")  # Escribe en el archivo
txt.close()  # Cierra el archivo

# Lectura completa del archivo
txt = open("archivo.txt", "r")
txt.seek(6) # Mueve el puntero/cursor al byte 6
contenido = txt.read()  # Lee todo el contenido
txt.close()
print(contenido)  # Muestra en pantalla mundo!

# Lectura línea por línea
with open("archivo.txt", "r") as txt:  # with cierra automáticamente
    linea = txt.readline()  # Lee una línea
    print(linea)  # Muestra la línea leída
# Al salir del bloque with, el archivo se cierra automáticamente
# No es necesario llamar a close()

# Leer todas las líneas como lista y escribirlas en otro archivo
with open("archivo.txt", "r") as txt:
    with open("archivo-copia.txt", "w") as copia:
        lineas = txt.readlines()  # Lista con todas las líneas
        copia.writelines(lineas)  # Escribe todas las líneas en otro archivo

# Iterar sobre las líneas
with open("archivo.txt", "r") as txt:
    for linea in txt:
        print(linea, end="")  # Muestra línea por línea

# Añadir contenido a un archivo
with open("archivo.txt", "a") as txt:
    txt.write("Nueva línea añadida.\n")  # Agrega sin borrar el contenido existente

Recursos

SQLite

SQLite es un motor de bases de datos SQL ligero, rápido y autónomo. Almacena toda la información (tablas, registros, etc.) en un único archivo con extensión .sql.

Es el motor de bases de datos más usado en el mundo, integrado en todos los teléfonos móviles, la mayoría de los ordenadores y muchas aplicaciones.

Sus archivos son estables, funcionan en múltiples plataformas y conservan compatibilidad con versiones anteriores, con el compromiso de mantener estas cualidades hasta 2050. Se usan para transferir datos entre sistemas y como almacenamiento a largo plazo.

El código fuente de SQLite es de dominio público y puede usarse libremente para cualquier propósito.

CRUD

En este script vemos lo básico para interactuar con SQLite, lo que habitualmente se le conoce como CRUD (CREATE, READ, UPDATE, DELETE).

import sqlite3  # Importar la biblioteca

# Conectar a una base de datos (crea el archivo si no existe)
conn = sqlite3.connect("mi_base.db")

# Crear un cursor para ejecutar comandos SQL
cur = conn.cursor()

# Crear una tabla
cur.execute("""
CREATE TABLE IF NOT EXISTS usuarios (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    nombre TEXT NOT NULL,
    edad INTEGER
)
""")

# Insertar datos en la tabla
cur.execute("INSERT INTO usuarios (nombre, edad) VALUES (?, ?)", ("Alice", 30))
cur.execute("INSERT INTO usuarios (nombre, edad) VALUES (?, ?)", ("Bob", 25))
conn.commit()  # Guardar los cambios

# Leer datos de la tabla
cur.execute("SELECT * FROM usuarios")
filas = cur.fetchall()  # Obtener todos los resultados
print(filas)  # [(1, 'Alice', 30), (2, 'Bob', 25)]

# Leer solo una fila
cur.execute("SELECT * FROM usuarios WHERE nombre = ?", ("Alice",))
fila = cur.fetchone()
print(fila)  # (1, 'Alice', 30)

# Actualizar datos
cur.execute("UPDATE usuarios SET edad = ? WHERE nombre = ?", (31, "Alice"))
conn.commit()  # Guardar cambios

# Eliminar un registro
cur.execute("DELETE FROM usuarios WHERE nombre = ?", ("Bob",))
conn.commit()

# Iterar sobre los resultados de una consulta
cur.execute("SELECT * FROM usuarios")
for fila in cur:
    print(fila)  # Muestra cada usuario línea por línea

# Cerrar cursor no es necesario, pero recomendable
cur.close()
# Cerrar conexión, es obligatorio
conn.close()

Es importante acordarse de cerrar tanto la connexión con la bbdd como el cursor, con el objetivo de evitar fugas de memoria. Tradicionalmente se realizaba de forma manual, pero actualmente se puede aprovechar el contexto que ofrece with y contextlib.closing.

from contextlib import closing

with closing(sqlite3.connect("mi_base.db")) as connection:
    with closing(connection.cursor()) as cursor:
        rows = cursor.execute("SELECT `id`, `nombre`, `edad` FROM usuarios").fetchall()
        print(rows)
    # cursor.close() # es automático
# connection.close() # es automático

Recursos

Módulos y Paquetes

A medida que main.py crece, es fundamental organizar el código dividiéndolo según su funcionalidad.

  • Módulos: Son archivos .py que contienen funciones, clases o variables reutilizables. Se importan con import nombre_modulo.
  • Paquetes: Son carpetas que agrupan módulos relacionados. Deben incluir un archivo __init__.py (puede estar vacío) para que Python los reconozca como paquetes.

Esto facilita la mantenibilidad y reutilización del código. Veamos un ejemplo

mi_proyecto/
│── main.py           # Archivo principal
│── utilidades.py     # Módulo con funciones auxiliares
│
├── paquete/          # Paquete con varios módulos
│   │── __init__.py  # Indica que es un paquete
│   │── modulo1.py   # Módulo con funciones específicas
│   │── modulo2.py   # Otro módulo con más funciones

Para hacer uso de nuestros módulos en el fichero main.py tendríamos que importarlos de la siguiente manera:

import utilidades
from paquete import modulo1
from paquete.modulo2 import funcion1

if __name__ == "__main__":
    # ejecutamos una funcion definida en el modulo de utilidades
    utilidades.funcion1(args)
    # de la misma manera podemos ejecutar una funcion del modulo1
    modulo1.funcion1(args, **kwargs)
    resultado = funcion1(**kwargs) # esta funcion es la definida en el modulo2

Python posee modulos definidos en la librería estándar como la librería math. Que aunque no esté en nuestro árbol de ficheros del proyecto, también puede ser importado.

# Puedes importar
import math
print(math.sqrt(16)) #=> 4.0

# Puedes obtener funciones específicas desde un módulo
from math import ceil, floor
print(ceil(3.7)) #=> 4.0
print(floor(3.7))#=> 3.0

# Puedes importar todas las funciones de un módulo
# Precaución: Esto no es recomendable
from math import *

# Puedes acortar los nombres de los módulos como alias
import math as m
math.sqrt(16) == m.sqrt(16) #=> True

# Puedes encontrar que funciones y atributos definen un módulo.
import math
dir(math)

Expresiones regulares

Las expresiones regulares (regex) son secuencias de caracteres que forman un patrón de búsqueda utilizado para encontrar, validar, extraer o reemplazar texto dentro de cadenas. Se usan en programación y procesamiento de texto para tareas como la validación de textos (correos, números de teléfono, hashtags...), la búsqueda de palabras clave en documentos o la transformación de datos.

Por ejemplo, la expresión regular \d+ busca uno o más dígitos en un texto, mientras que ^[A-Za-z]+$ verifica si una cadena contiene solo letras.

import re  # Importa el módulo de expresiones regulares

# Coincidencia exacta con 'match' (busca solo al inicio)
patron = r"hola"
texto = "hola mundo"
coincidencia = re.match(patron, texto)
coincidencia is not None  # => True
coincidencia.start()    # => 0 # Nos informa que empieza en la posición 0
coincidencia.end()    # => 4 # Nos informa que termina en la posición 4

# 'search' busca en todo el texto
patron = r"mundo"
coincidencia = re.search(patron, texto)
coincidencia is not None  # => True
coincidencia.span() # => (5, 10) # Nos informa que empieza en 5 y termina en 10 en forma de tupla

# 'findall' devuelve todas las coincidencias en una lista
texto = "uno dos tres dos"
patron = r"dos"
re.findall(patron, texto)  # => ['dos', 'dos']

# 'sub' reemplaza patrones en el texto
texto = "Me gusta el color azul"
patron = r"azul"
re.sub(patron, "rojo", texto)  # => "Me gusta el color rojo"

# Grupos de captura con paréntesis
texto = "Mi teléfono es 123-456-7890"
patron = r"(\d{3})-(\d{3})-(\d{4})"
coincidencia = re.search(patron, texto)
coincidencia.groups()  # => ('123', '456', '7890')

# Modificadores como re.IGNORECASE (ignorar mayúsculas/minúsculas)
patron = r"python"
texto = "Me gusta PYTHON"
re.search(patron, texto, re.IGNORECASE) is not None  # => True

# Patrones útiles
# .  : Comodín punto. Cualquier caracter
# [^] : Negación. (dentro de los corchetes)
# ^  : Comienza por... (si es al principio de la expresión regular)
# $  : Finaliza por...
# \d : dígitos (0-9)
# \w : caracteres alfanuméricos (a-z, A-Z, 0-9, _)
# \s : espacios en blanco (espacio, tabulación, salto de línea)
# +  : uno o más
# *  : cero o más
# ?  : cero o uno
# [-]  : rango. [a-z](de la a a la z), [0-8](desde el 0 hasta el 8)
# [] : clases de caracteres. ¿Están esos caracteres en el texto sin importar el orden?
# {n,m} : entre n y m repeticiones

# Buscar un correo electrónico
texto = "Contacto: usuario@example.com"
patron = r"[\w.-]+@[\w.-]+\.[a-zA-Z]{2,}"
re.search(patron, texto).group()  # => 'usuario@example.com'

Recursos

Ejercicios

Aqui tenemos una lista de ejercicios para prácticar de forma autónoma.

Expresiones regulares

  1. Validar si una cadena contiene la palabra "Python".

    • Entrada: "Me encanta Python"Salida: True
  2. Comprobar si una cadena empieza con "Hola".

    • Entrada: "Hola, mundo!"Salida: True
  3. Buscar todas las palabras de una oración.

    • Entrada: "El sol brilla en el cielo"Salida: ['El', 'sol', 'brilla', 'en', 'el', 'cielo']
  4. Reemplazar todos los espacios por guiones bajos.

    • Entrada: "Expresiones regulares en Python"Salida: "Expresiones_regulares_en_Python"
  5. Extraer todos los números de una cadena.

    • Entrada: "La casa 123 cuesta 45000 euros"Salida: ['123', '45000']
  6. Comprobar si una cadena contiene solo letras y números.

    • Entrada: "Python3"Salida: True
    • Entrada: "Python 3"Salida: False
  7. Validar una dirección de correo electrónico sencilla.

    • Entrada: "correo@example.com"Salida: True
  8. Extraer todas las palabras que empiezan con una vocal.

    • Entrada: "Esto es una prueba de regex"Salida: ['Esto', 'es', 'una']
  9. Validar un número de teléfono español (formato +34 600 123 456).

    • Entrada: "+34 600 123 456"Salida: True
    • Entrada: "+34600123456"Salida: True
    • Entrada: "600 123 456"Salida: True
    • Entrada: "600123456"Salida: True
  10. Reemplazar todas las vocales por un asterisco.

    • Entrada: "Hola Mundo"Salida: "H*l* M*nd*"
  11. Validar una dirección IP en formato IPv4 (192.168.1.1).

    • Entrada: "192.168.1.1"Salida: True
  12. Extraer todas las direcciones de correo electrónico de un texto.

    • Entrada: "Contacta a support@mail.com o info@empresa.org"Salida: ['support@mail.com', 'info@empresa.org']
  13. Validar una contraseña segura (mín. 8 caracteres, una mayúscula mínimo, un número mínimo y un símbolo mínimo).

    • Entrada: "P@ssw0rd!"Salida: True
  14. Extraer todas las etiquetas HTML de un fragmento de código.

    • Entrada: "<h1>Hola</h1> <p>Esto es un párrafo</p>"Salida: ['<h1>', '</h1>', '<p>', '</p>']
  15. Encontrar todas las fechas en formato DD/MM/AAAA en un texto.

    • Entrada: "Nací el 12/05/1995 y mi hermano el 23/08/2000"Salida: ['12/05/1995', '23/08/2000']
  16. Una hora en el formato HH:mm.

    • Entrada: "23:00"Salida: True
  17. Extraer los hashtags de un tweet.

    • Entrada: "Me encanta #Python y #regex"Salida: ['#Python', '#regex']
  18. Detectar enlaces web en un texto (http:// o https://).

    • Entrada: "Visita https://google.com o http://example.com"Salida: ['https://google.com', 'http://example.com']
  19. Extraer el contenido de una etiqueta HTML.

    • Entrada: "<h1>Hola</h1>"Salida: "Hola"
  20. Extraer el dominio de una URL (https://www.google.comgoogle.com).

    • Entrada: "https://www.openai.com"Salida: "openai.com"
  21. Extraer los datos de una linea de CSV, siendo las columnas: ID;nombre;nacimiento.

    • Entrada: "1;Ana Belén;27/04/1960"Salida: ['1', 'Ana Belén', '27/04/1960']

Persistencia

  1. Escribir un programa que pida una frase al usuario y la guarde en un fichero llamado frase.txt. Luego, leer el contenido del fichero y mostrarlo en pantalla.

    • Entrada: "Hola, mundo"Salida: "Hola, mundo"
  2. Escribir una función que pida un número entero entre 1 y 10 y guarde en un fichero tabla-n.txt la tabla de multiplicar de ese número.*

    • Entrada: 5Salida: tabla-5.txt creado
  3. Escribir una función que pida un número entre 1 y 10, lea el fichero tabla-n.txt y lo muestre en pantalla. Si no existe, mostrar un mensaje de error.

  4. Escribir una función que pida dos números n y m, lea el fichero tabla-n.txt y muestre la línea m.

  5. Escribir un programa que pida la ruta a un fichero cualquiera y cuente cuántas palabras contiene en total.

  6. Crear un programa para gestionar listin.txt con nombres y teléfonos separados por comas, por ejemplo nombre;555555555. Permitir agregar, consultar y eliminar contactos.

  7. Escribir un programa que pregunte al usuario la ruta de un archivo de origen y copie el contenido de /ruta/a/origen.txt a la ruta seleccionada por el usuario en /ruta/a/destino.txt.

  8. Escribir un programa que lea un fichero y cuente cuántas veces aparece cada palabra y el total de palabras existentes en el texto.

  9. Escribir un programa que lea un fichero y muestre sólo las líneas que contengan una palabra dada por el usuario.

  10. Crear un fichero numeros.txt con una lista de 1000 números generados aleatoriamente (math.randint) y escribir un programa que busque si un número dado por el usuario está en la lista.

  11. Leer un fichero de texto y escribir su contenido en otro fichero con las oraciones ordenadas alfabéticamente. Las oraciones están separadas por punto (.), exclamación (!), interrogante (?).

  12. Escribir un programa que extienda un fichero con su mismo contenido al revés.

  13. Escribir un programa que lea un fichero con números separados por saltos de linea y los ordene de menor a mayor.

  14. Escribir un programa que lea un fichero CSV y lo convierta en un fichero JSON con el mismo contenido. Con las claves del encabezado del CSV.

Telemáticas

Se recoge en esta sección la información de las tutorías telemáticas.

28 de Noviembre de 2024

En la tutoría de hoy se revisaron los siguientes temas:

Operadores Aritméticos

Los operadores aritméticos se utilizan para realizar operaciones matemáticas básicas como suma, resta, multiplicación, división, etc.

Ejemplo de Código

num_a = 5
num_b = 2

suma = num_a + num_b
print(f"num_a + num_b = {suma}")

resta = num_a - num_b
print(f"num_a - num_b = {resta}")

multiplicacion = num_a * num_b
print(f"num_a * num_b = {multiplicacion}")

division = num_a / num_b
print(f"num_a / num_b = {division}")

division_entera = num_a // num_b
print(f"num_a // num_b = {division_entera}")

modulo = num_a % num_b
print(f"num_a % num_b = {modulo}")

potencia = num_a ** num_b
print(f"num_a ** num_b = {potencia}")

Explicación

  • +: Suma dos números.
  • -: Resta dos números.
  • *: Multiplica dos números.
  • /: Realiza una división y devuelve un número de punto flotante.
  • //: Realiza una división entera, descartando los decimales.
  • %: Calcula el resto de la división (módulo).
  • **: Calcula la potencia de un número.

Operadores Lógicos

Los operadores lógicos permiten combinar condiciones que devuelven valores booleanos (True o False).

Ejemplo de Código

print(f"True and False is {True and False}")
print(f"True or False is {True or False}")
print(f"not True is {not True}")
print(f"not False is {not False}")

Explicación

  • and: Devuelve True si ambas condiciones son True.
  • or: Devuelve True si al menos una condición es True.
  • not: Invierte el valor lógico. Si es True, lo convierte en False y viceversa.

Operadores Relacionales

Los operadores relacionales comparan dos valores y devuelven True o False.

Ejemplo de Código

num_a = 5
num_b = 3

if num_a < num_b:
    print(f"{num_a} es menor que {num_b}")
else:
    print(f"{num_a} es mayor o igual a {num_b}")

Explicación

  • <: Devuelve True si el primer valor es menor que el segundo.
  • >: Devuelve True si el primer valor es MAYOR que el segundo.

Sentencias Condicionales

Las sentencias condicionales permiten ejecutar diferentes bloques de código según una condición.

Ejemplo de Código

num = -5
if num < 0:
    print(f"{num} es negativo")
elif num == 0:
    print(f"{num} es cero")
else:
    print(f"{num} es positivo")

Explicación

  • if: Evalúa la primera condición.
  • elif: Evalúa condiciones adicionales si las anteriores son False.
  • else: Se ejecuta si ninguna de las condiciones anteriores es True.

Los bloques de código se definen mediante la indentación. Python utiliza la indentación en lugar de llaves {} para definir bloques de código. Pueden existir múltiples bloques elif pero sólo un bloque else. Tanto elif como else son opcionales por lo que un if puede estar solo, pero un elif o else debe estar precedido por un if forzosamente.

Las sentencias condicionales pueden anidarse, es decir, un bloque if puede contener otro bloque if.

Ejemplo de Código

num = 5
if num >= 0:
    if num == 0:
        print(f"{num} es cero")
    else:
        print(f"{num} es positivo")
else:
    print(f"{num} es negativo")

Nota: La anidación excesiva puede hacer que el código sea difícil de leer y mantener. Se recomienda mantener la anidación al mínimo. Evitemos la anidación excesiva de sentencias condicionales.

Anidación de Sentencias Condicionales


Operadores Bit a Bit

Los operadores bit a bit trabajan directamente con los bits de los números.

Ejemplo de Código

and_bitwise = 1 & 0
print(f"1 & 0 = {and_bitwise}")

or_bitwise = 1 | 0
print(f"1 | 0 = {or_bitwise}")

xor_bitwise = 1 ^ 0
print(f"1 ^ 0 = {xor_bitwise}")

xor_bitwise = 1 ^ 1
print(f"1 ^ 1 = {xor_bitwise}")

not_bitwise = ~1
print(f"~1 = {not_bitwise}")

left_shift = 1 << 2
print(f"1 << 2 = {left_shift}")

right_shift = 6 >> 2
print(f"6 >> 2 = {right_shift}")

Explicación

  • &: AND bit a bit. Devuelve 1 solo si ambos bits son 1.
  • |: OR bit a bit. Devuelve 1 si al menos uno de los bits es 1.
  • ^: XOR bit a bit. Devuelve 1 si solo uno de los bits es 1.
  • ~: NOT bit a bit. Invierte todos los bits.
  • <<: Desplaza los bits a la izquierda. Multiplica por 2 por cada desplazamiento.
  • >>: Desplaza los bits a la derecha. Divide por 2 por cada desplazamiento.

Función Principal

El siguiente código define una función principal main que se ejecuta exclusivamente si el archivo se está ejecutando directamente.

Ejemplo de Código

print("Se ejecuta siempre.")

def main():
    print("Se ejecuta exclusivamente al invocar el programa de forma directa.")

if __name__ == "__main__":
    main()

Explicación

  • def main(): Define una función llamada main.
  • if __name__ == "__main__":: Este bloque se ejecuta solo si el archivo se ejecuta directamente, no si es importado como módulo.