9  Programación en Python

Python (Van Rossum y Drake 2009) es un lenguaje de programación de alto nivel, interpretado y de propósito general, famoso por su legibilidad y sintaxis clara, que favorece un código más limpio y comprensible (Scopatz y Huff 2015). Desde su creación en 1991 por Guido van Rossum (CodeRivers 2025), Python se ha expandido más allá de sus humildes comienzos para convertirse en uno de los pilares de la programación moderna, especialmente en los campos de la ciencia de datos (CD) y la inteligencia artificial (IA).

La importancia de Python en la CD y la IA se debe a varias razones clave. En primer lugar, su sintaxis sencilla y legible hace que sea accesible para profesionales de diversos campos, no solo para programadores experimentados. Esto ha permitido a un amplio espectro de científicos, investigadores y analistas de datos adoptar Python para el análisis y la visualización de datos, sin tener que invertir tiempo considerable en aprender las complejidades de la programación.

Además, Python se ha beneficiado enormemente de una comunidad activa y comprometida que ha desarrollado una vasta colección de bibliotecas y frameworks especializados. En el ámbito de la ciencia de datos, bibliotecas como NumPy, pandas, Matplotlib y SciPy han simplificado el manejo de grandes conjuntos de datos, el análisis numérico y la visualización de datos. En el terreno de la IA y el aprendizaje automático, bibliotecas como TensorFlow, Keras y PyTorch (Paszke et al. 2019) ofrecen herramientas potentes y flexibles para la construcción y el entrenamiento de modelos de IA, desde redes neuronales simples hasta sistemas complejos de aprendizaje profundo.

La versatilidad de Python también juega un papel crucial en su popularidad. Puede ser utilizado para desarrollar prototipos rápidos y también en aplicaciones de producción a gran escala, lo que lo hace ideal para la investigación y el desarrollo en ciencia de datos e IA, donde la experimentación y la iteración rápida son comunes. La capacidad de Python para integrarse con otros lenguajes y tecnologías facilita la implementación de soluciones complejas que requieren la combinación de bibliotecas y herramientas específicas del dominio.

En resumen, Python se ha convertido en un lenguaje fundamental para la ciencia de datos y la inteligencia artificial debido a su simplicidad, su amplio ecosistema de bibliotecas especializadas, y su comunidad activa y de apoyo. Estos factores han colaborado para que Python sea no solo accesible para los principiantes, sino también lo suficientemente poderoso para los profesionales y investigadores que buscan empujar los límites de lo posible en sus campos.

9.1 Estructuras de control

En Python, las estructuras de control permiten modificar el flujo de ejecución de un programa. Estas estructuras son esenciales para tomar decisiones, repetir bloques de código y escribir programas más dinámicos y útiles.

9.1.1 Decisión

La estructura de decisión más común en Python es la instrucción if, que evalúa una condición booleana. Dependiendo de si la condición es verdadera o falsa, el programa ejecuta uno u otro bloque de código.

x = 10
if x > 0:
    print("El numero es positivo")
elif x == 0:
    print("El numero es cero")
else:
    print("El numero es negativo")

El uso de elif y else permite abarcar múltiples escenarios, lo que hace que el código sea más expresivo y fácil de mantener.

9.1.2 Ciclos

Los ciclos o bucles permiten repetir instrucciones múltiples veces sin necesidad de escribir el mismo código repetidamente. En Python existen dos tipos principales de bucles:

  • for: se utiliza para iterar sobre elementos de una secuencia (listas, cadenas, rangos, etc.).
  for i in range(5):
      print("Iteracion:", i)
  • while: repite un bloque de código mientras una condición booleana se mantenga verdadera.
  contador = 0
  while contador < 5:
      print("Contador:", contador)
      contador += 1

Los bucles pueden combinarse con instrucciones como break (para salir de un ciclo antes de tiempo) o continue (para saltar a la siguiente iteración).

9.2 Comprensiones

Las comprensiones son una característica poderosa y concisa de Python para crear colecciones (listas, conjuntos o diccionarios) en una sola línea, aplicando transformaciones o condiciones sobre iterables.

  • Comprensiones de listas:
  cuadrados = [x**2 for x in range(10)]
  print(cuadrados)
  • Comprensiones de conjuntos:
  pares = {x for x in range(20) if x % 2 == 0}
  print(pares)
  • Comprensiones de diccionarios:
  cuadrados_dict = {x: x**2 for x in range(5)}
  print(cuadrados_dict)

Las comprensiones no solo hacen el código más compacto, sino que también mejoran la legibilidad y permiten expresar operaciones complejas de forma clara y elegante.

9.3 Numpy

NumPy, que es la abreviatura de Numerical Python, es un paquete fundamental en el ecosistema de Python, especialmente diseñado para realizar operaciones numéricas y matemáticas de manera eficiente. Este paquete se ha convertido en la piedra angular de muchas aplicaciones científicas y de ingeniería debido a su capacidad para trabajar con arreglos y matrices de datos a gran velocidad, superando con creces las capacidades de las listas de Python estándar en términos de rendimiento y funcionalidad.

El corazón de NumPy es el objeto ndarray, que representa una colección homogénea y multidimensional de elementos (todos del mismo tipo), permitiendo realizar operaciones matemáticas complejas sobre grandes volúmenes de datos con relativa facilidad y eficiencia. A diferencia de las listas en Python, que pueden contener elementos de diferentes tipos y tienen una flexibilidad inherente, los arreglos de NumPy están optimizados para cálculos numéricos pesados y operaciones vectorizadas que aplican una operación sobre todos los elementos del arreglo sin la necesidad de bucles explícitos.

Además de su poderoso objeto de arreglo, NumPy ofrece un vasto conjunto de funciones matemáticas que permiten realizar desde operaciones aritméticas básicas hasta cálculos matemáticos más complejos como transformadas de Fourier, álgebra lineal, y generación de números aleatorios. Esta amplia gama de herramientas matemáticas, junto con la capacidad de integrarse sin problemas con otras bibliotecas científicas de Python, hace de NumPy un componente esencial en el análisis de datos, la modelización científica, la simulación, y mucho más.

A continuación se presentan ejemplos básicos para la creación y manipulación de arreglos:

9.3.1 Arreglos

En NumPy, un array se define como una estructura de datos central que permite almacenar y manipular grandes conjuntos de datos numéricos de manera eficiente. Esta estructura es conocida como ndarray (abreviatura de “N-dimensional array”), que representa una colección homogénea de elementos; es decir, todos los elementos deben ser del mismo tipo. Los arrays de NumPy son n-dimensionales, lo que significa que pueden representar no solo vectores y matrices (1D y 2D respectivamente), sino también estructuras de datos de mayor dimensionalidad.

Para definir un array en NumPy, primero se debe importar la biblioteca NumPy, típicamente con el alias np para facilitar su uso. Luego, se puede utilizar la función np.array() para crear un array, pasando una lista (o una lista de listas para dimensiones superiores) de elementos como argumento. Esta lista define los contenidos del array, y NumPy automáticamente determina el tipo de datos más adecuado para los elementos contenidos.

Por ejemplo, para crear un simple array unidimensional, se pasaría una única lista de números. Para un array bidimensional, que podría representar una matriz matemática, se pasaría una lista de listas, donde cada sublista representa una fila de la matriz. NumPy ofrece una gran flexibilidad en la definición de arrays, permitiendo especificar explícitamente el tipo de datos de los elementos mediante el argumento dtype si se desea un control más fino sobre el comportamiento del array.

Además de np.array(), NumPy proporciona una serie de funciones para generar arrays de formas particulares y con contenidos predefinidos. Por ejemplo, np.zeros() crea un array lleno de ceros, np.ones() un array de unos, y np.arange() genera un array con una secuencia de números dentro de un rango especificado. Estas funciones son especialmente útiles para crear arrays que sirven como punto de partida para cálculos más complejos.

Una vez definido, el array de NumPy se puede manipular mediante una amplia gama de operaciones matemáticas y lógicas, permitiendo realizar desde simples sumas hasta operaciones más complejas como multiplicaciones de matrices o transformadas de Fourier, todo ello de manera muy eficiente gracias a la optimización interna de NumPy y a su capacidad para operar sobre todos los elementos del array de manera simultánea (un proceso conocido como vectorización).

import numpy as np
# 1. Creacion de un array unidimensional (vector)
vector = np.array([1, 2, 3, 4, 5])
# 2. Creacion de un array bidimensional (matriz)
matriz = np.array([[1, 2, 3], [4, 5, 6]])
# 3. Uso de funciones de conveniencia
ceros = np.zeros((3, 3))       # Matriz de 3x3 llena de ceros
identidad = np.eye(4)          # Matriz identidad de 4x4
espaciado = np.linspace(0, 10, 5) # 5 numeros equiespaciados entre 0 y 10
# 4. Operaciones vectorizadas (sin necesidad de ciclos for)
a = np.array([10, 20, 30])
b = np.array([1, 2, 3])
suma = a + b    # Resultado: [11, 22, 33]
potencia = a**2 # Eleva cada elemento al cuadrado

9.3.2 Indexado y rebanado

El acceso a los datos en los arreglos de NumPy es extremadamente flexible y potente. A diferencia de las secuencias estándar de Python, NumPy permite el uso de múltiples índices, rangos complejos y máscaras booleanas. El slicing (rebanado) crea una vista del arreglo original, lo que significa que no se copia el contenido en memoria a menos que se solicite explícitamente, optimizando así el uso de recursos en conjuntos de datos masivos.

Una de las características más potentes es el indexado booleano, que permite filtrar elementos que cumplen con una condición específica. Esto es particularmente útil en el preprocesamiento de datos para eliminar valores atípicos o seleccionar subconjuntos de muestras basados en criterios lógicos.

import numpy as np
# Creacion de una matriz de 4x4 para el ejemplo
data = np.array([
    [10, 20, 30, 40],
    [50, 60, 70, 80],
    [90, 100, 110, 120],
    [130, 140, 150, 160]
])
# 1. Indexado basico y multidimensional
elemento = data[1, 2]        # Fila 1, Columna 2 -> 70
fila_completa = data[2, :]   # Toda la tercera fila -> [90, 100, 110, 120]
col_especifica = data[:, 1]  # Toda la segunda columna -> [20, 60, 100, 140]
# 2. Slicing (Rebanado)
# Extraer la sub-matriz central de 2x2
sub_matriz = data[1:3, 1:3]  # [[60, 70], [100, 110]]
# 3. Indexado Booleano (Filtrado)
# Seleccionar solo los elementos mayores a 100
mayores_cien = data[data > 100]
# 4. Fancy Indexing (Indexado elegante)
# Seleccionar filas especificas en un orden determinado
filas_remotas = data[[3, 0, 2]] # Retorna las filas 3, 0 y 2 en ese orden

9.3.3 Replicado (Broadcasting)

El broadcasting es una de las características más potentes de NumPy, ya que permite realizar operaciones aritméticas entre arreglos que tienen diferentes formas (shapes), siempre que cumplan con ciertas reglas de compatibilidad. En lugar de requerir que ambos arreglos tengan dimensiones idénticas, NumPy “transmite” o expande virtualmente el arreglo más pequeño a través del más grande para que las formas sean compatibles, permitiendo realizar la operación elemento a elemento sin realizar copias innecesarias de los datos en memoria.

Este mecanismo es fundamental en el desarrollo de algoritmos de aprendizaje profundo, por ejemplo, al sumar un vector de sesgo (bias) a una matriz de activaciones, o al normalizar un conjunto de datos restando la media de cada columna. El broadcasting hace que el código sea más conciso, legible y eficiente desde el punto de vista computacional.

import numpy as np
# 1. Escalar y Array (El caso mas simple de broadcasting)
a = np.array([1, 2, 3])
resultado_escalar = a * 2  # El escalar 2 se "estira" a [2, 2, 2]
# Resultado: [2, 4, 6]
# 2. Sumar un vector a una matriz
# Matriz de 3x3
matriz = np.array([
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
])
# Vector de 1x3
vector = np.array([10, 20, 30])
# El vector se suma a cada fila de la matriz
resultado_matriz = matriz + vector
# Resultado:
# [[11, 22, 33],
#  [14, 25, 36],
#  [17, 28, 39]]
# 3. Broadcasting en dimensiones compatibles
# Columna de (3, 1) y Fila de (1, 3) resulta en matriz de (3, 3)
columna = np.array([[0], [10], [20]])
fila = np.array([1, 2, 3])
tabla_suma = columna + fila
# Resultado:
# [[ 1,  2,  3],
#  [11, 12, 13],
#  [21, 22, 23]]

9.4 Programación orientada a objetos

La programación orientada a objetos (POO) es un paradigma que organiza el código en torno a objetos, los cuales combinan tanto datos (atributos) como funciones (métodos) en una sola entidad. Python soporta plenamente este paradigma y lo hace de manera sencilla y legible, lo que lo convierte en una excelente opción para aprender y aplicar los principios de la POO.

En POO, una clase actúa como plantilla para crear objetos. A partir de ella se pueden instanciar múltiples objetos con características y comportamientos comunes. Los cuatro pilares fundamentales de la programación orientada a objetos son:

  • Abstracción: permite modelar entidades del mundo real en clases y objetos.

  • Encapsulamiento: agrupa datos y comportamientos en una misma unidad, protegiendo los atributos mediante convenciones.

  • Herencia: posibilita que una clase derive de otra, reutilizando y extendiendo su funcionalidad.

  • Polimorfismo: permite usar una misma interfaz para distintos tipos de objetos, adaptando el comportamiento según la implementación.

Definición de clases y objetos

En Python, una clase se define con la palabra clave class. El método especial __init__ se utiliza para inicializar los atributos de un objeto al momento de su creación.

class Persona:
    def __init__(self, nombre, edad):
        self.nombre = nombre
        self.edad = edad
    
    def saludar(self):
        print(f"Hola, mi nombre es {self.nombre} y tengo {self.edad} anyos.")
# Crear un objeto a partir de la clase
persona1 = Persona("Ana", 25)
persona1.saludar()

Herencia

La herencia permite crear nuevas clases basadas en clases existentes. La clase hija hereda atributos y métodos de la clase padre, pero también puede añadir o sobrescribir funcionalidades.

class Estudiante(Persona):
    def __init__(self, nombre, edad, matricula):
        super().__init__(nombre, edad)
        self.matricula = matricula
    
    def estudiar(self):
        print(f"{self.nombre} esta estudiando.")
est1 = Estudiante("Luis", 20, "A12345")
est1.saludar()
est1.estudiar()

Polimorfismo

El polimorfismo en Python se aprecia cuando diferentes clases implementan métodos con el mismo nombre, pero cada uno con un comportamiento distinto. Esto permite escribir código más genérico y reutilizable.

class Gato:
    def sonido(self):
        return "Miau"
class Perro:
    def sonido(self):
        return "Guau"
animales = [Gato(), Perro()]
for animal in animales:
    print(animal.sonido())

En resumen, la programación orientada a objetos en Python facilita la construcción de programas más estructurados, modulares y reutilizables, lo cual es esencial en proyectos grandes de ciencia de datos, inteligencia artificial y desarrollo de software en general.

9.5 Ejercicios

Ejercicio

Escriba un programa usando numpy que obtenga la transpuesta de una matriz sin usar una función preestablecida.

Ejercicio

Crea una clase base figura con un método abstracto area(). Deriva clases circulo y rectángulo que implementen dicho método. Demuestra el polimorfismo creando una lista de figuras y calculando el área total.

Ejercicio

Crea un arreglo de NumPy de \(5x5\) con valores aleatorios. Escribe un programa que normalice estos valores (restando la media y dividiendo por la desviación estándar de todo el arreglo).

Ejercicio

Dado un arreglo bidimensional, utiliza indexación booleana para reemplazar todos los elementos que sean mayores a un valor \(N\) por el promedio de los elementos que son menores o iguales a \(N\).

Ejercicio

Crea dos arreglos unidimensionales del mismo tamaño. Escribe un programa que calcule el producto punto entre ambos utilizando únicamente un bucle for y compruébalo comparando el resultado con la función numpy.dot().

CodeRivers. 2025. «Python: A Deep Dive into Its Development Timeline and Practical Applications». 26 de enero de 2025. https://coderivers.org/blog/when-python-was-developed/.
Paszke, Adam, Sam Gross, Francisco Massa, Adam Lerer, James Bradbury, Gregory Chanan, Trevor Killeen, et al. 2019. «PyTorch: An Imperative Style, High-Performance Deep Learning Library». En Advances in Neural Information Processing Systems. Vol. 32.
Scopatz, Anthony, y Kathryn D Huff. 2015. Effective computation in physics: Field guide to research with python. " O’Reilly Media, Inc.".
Van Rossum, Guido, y Fred L. Drake. 2009. Python 3 Reference Manual. CreateSpace.