Explorando Python 3: Conceptos Clave y Aplicaciones Fundamentales

Interacción Básica con Python y el Entorno de Desarrollo

Python es un lenguaje versátil que permite interactura de diversas maneras, desde la ejecución de scripts hasta la consola interactiva. Comprender cómo iniciar y salir del intérprete, así como manejar argumentos de línea de comandos y funciones de utilidad, es fundamental para cualquier desarrollador.

Consola Interactiva de Python

Para iniciar el intérprete interactivo de Python desde la terminal de su sistema (por ejemplo, Ubuntu o cualquier otro sistema operativo), simplemente escriba python3 (o python si esa es la versión predeterminada).

$ python3
Python 3.x.x (default, ...)
[GCC ...] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

Para salir del intérprete, puede usar la función quit() o presionar Ctrl+D (en sistemas Unix/Linux) o Ctrl+Z seguido de Enter (en Windows).

>>> quit()
$

Ejecución de Scripts de Python

Los scripts de Python se ejecutan pasando el nombre del archivo al intérprete:

$ python3 mi_script.py

Argumentos de Línea de Comandos (sys.argv)

El módulo sys proporciona acceso a variables y funciones que interactúan fuertemente con el intérprete. Una de sus utilidades es sys.argv, una lista de cadenas que contiene los argumentos de línea de comandos pasados a un script de Python. El primer elemento de sys.argv es siempre el nombre del script.

Considere un script llamado procesar_argumentos.py:

# procesar_argumentos.py
import sys

print(f"Nombre del script: {sys.argv[0]}")
print(f"Número total de argumentos (incluyendo el script): {len(sys.argv)}")
print("Argumentos pasados:")
for i, arg in enumerate(sys.argv[1:]):
    print(f"  Argumento {i+1}: {arg}")

if __name__ == '__main__':
    # Este bloque solo se ejecuta cuando el script se ejecuta directamente
    print("\nEste mensaje aparece solo al ejecutar el script directamente.")

Ejecución desde la terminal:

$ python3 procesar_argumentos.py primer_valor segundo_valor 42

Salida esperada:

Nombre del script: procesar_argumentos.py
Número total de argumentos (incluyendo el script): 4
Argumentos pasados:
  Argumento 1: primer_valor
  Argumento 2: segundo_valor
  Argumento 3: 42

Este mensaje aparece solo al ejecutar el script directamente.

El Bloque if __name__ == '__main__':

Este bloque es una convención común en Python. El intérprete de Python asigna el valor "__main__" a la variable especial __name__ cuando el script se ejecuta directamente. Si el script se importa como un módulo en otro script, __name__ contendrá el nombre del módulo. Esto permite que un archivo Python sirva tanto como un script ejecutable como una biblioteca importable.

Funciones de Utilidad

  • help(): Permite obtener ayuda sobre objetos, módulos, funciones o tipos. Puede usarlo dentro del intérprete (help(print)) o desde la terminal (pydoc3 print).
  • type(): Retorna el tipo de un objeto o permite crear un nuevo tipo. Por ejemplo, type("Hola") devolverá <class 'str'>.
  • os.path.exists(ruta): Del módulo os.path, verifica si una ruta de archivo o directorio existe. Retorna True o False.
  • range(): Genera una secuencia de números. Es importante recordar que range(inicio, fin) es un rango semiabierto [inicio, fin), excluyendo el valor final.

Manejo de Entrada y Salida

La interacción con el usuario y la presentación de información son pilares en cualquier aplicación. Python 3 ofrece herramientas robustas para estas tareas.

Salida: La Función print()

La función print() se utiliza para mostrar datos en la consola. En Python 3, print es una función y requiere paréntesis.

Impresión Básica

print("¡Hola, Python!")

Concatenación de Cadenas

Puede unir cadenas usando el operador +:

nombre = "Mundo"
print("Hola, " + nombre + "!")

Formato de Salida

Python 3 ofrece varias formas de formatear cadenas de salida, siendo los f-strings (cadenas literales formateadas) la forma más moderna y recomendada.

F-strings (Python 3.6+)

edad = 30
altura_cm = 175.5
print(f"Mi edad es {edad} años y mi altura es {altura_cm:.1f} cm.")

Método .format()

producto = "manzana"
precio = 1.25
print("El precio de la {} es {:.2f} €.".format(producto, precio))

Marcadores de Posición (Estilo C, compatible pero menos moderno)

ciudad = "Madrid"
temperatura = 25
print("La temperatura en %s es %d grados." % (ciudad, temperatura))

Caracteres Especiales

  • \n: Salto de línea.
  • \t: Tabulación.
print("Línea 1\nLínea 2\t(Tabulado)")

Entrada: La Función input()

La función input() se utiliza para obtener entrada del usuario desde la consola. Siempre devuelve la entrada como una cadena de texto.

nombre_usuario = input("Por favor, introduce tu nombre: ")
print(f"¡Hola, {nombre_usuario}!")

# Para usar la entrada como número, es necesario convertirla
edad_str = input("¿Cuántos años tienes? ")
try:
    edad_int = int(edad_str)
    print(f"Confirmado: tienes {edad_int} años.")
except ValueError:
    print("Entrada inválida. Por favor, introduce un número.")

Trabajo con Archivos

El manejo de archivos es crucial para la persistencia de datos. Python ofrece una interfaz sencilla para leer y escribir en archivos.

Apertura y Cierre de Archivos

La forma más segura de trabajar con archivos es utilizando la declaración with open(). Esto garantiza que el archivo se cierre automáticamente, incluso si ocurren errores.

Modos de Apertura

  • 'r': Modo de lectura (predeterminado). El archivo debe existir.
  • 'w': Modo de escritura. Trunca el archivo si existe; lo crea si no.
  • 'a': Modo de adición. Escribe al final del archivo; lo crea si no.
  • 'x': Modo de creación exclusiva. Falla si el archivo ya existe.
  • 'b': Modo binario (para archivos no textuales como imágenes). Se usa junto con otros modos, ej. 'rb', 'wb'.
  • '+': Modo de actualización (lectura y escritura). Se usa junto con otros modos, ej. 'r+', 'w+'.
# Ejemplo de escritura con 'w'
with open('ejemplo.txt', 'w', encoding='utf-8') as archivo:
    archivo.write("Esta es la primera línea.\n")
    archivo.write("Esta es la segunda línea.\n")

# Ejemplo de adición con 'a'
with open('ejemplo.txt', 'a', encoding='utf-8') as archivo:
    archivo.write("Esta es una nueva línea añadida.\n")

Lectura de Archivos

read(): Leer todo el contenido

Lee el archivo completo como una sola cadena de texto. Se puede pasar un argumento opcional para leer un número específico de caracteres/bytes.

with open('ejemplo.txt', 'r', encoding='utf-8') as archivo:
    contenido = archivo.read()
    print("--- Contenido completo ---")
    print(contenido)

readline(): Leer línea por línea

Lee una sola línea del archivo. Las llamadas sucesivas a readline() leerán las siguientes líneas.

with open('ejemplo.txt', 'r', encoding='utf-8') as archivo:
    print("--- Línea por línea con readline() ---")
    linea1 = archivo.readline()
    linea2 = archivo.readline()
    print(f"1: {linea1.strip()}") # .strip() para quitar el salto de línea al final
    print(f"2: {linea2.strip()}")

readlines(): Leer todas las líneas en una lista

Lee todas las líneas del archivo y las devuelve como una lista de cadenas, donde cada cadena es una línea del archivo (incluyendo el carácter de nueva línea).

with open('ejemplo.txt', 'r', encoding='utf-8') as archivo:
    lineas = archivo.readlines()
    print("--- Todas las líneas en una lista con readlines() ---")
    for i, linea in enumerate(lineas):
        print(f"Línea {i+1}: {linea.strip()}")

Escritura en Archivos

write(cadena): Escribir una cadena

Escribe la cadena proporcionada en el archivo. No añade automáticamente un salto de línea.

# Sobrescribir (o crear) un archivo y escribir varias líneas
with open('nuevo_archivo.txt', 'w', encoding='utf-8') as archivo:
    archivo.write("Primera línea escrita.\n")
    archivo.write("Segunda línea escrita.\n")
    archivo.write("Tercera línea escrita.\n")
print("Archivo 'nuevo_archivo.txt' creado y escrito.")

Posicionamiento en Archivos: seek() y tell()

El "puntero" del archivo indica la posición actual para la lectura o escritura.

  • tell(): Retorna la posición actual del puntero en el archivo (desplazamiento en bytes desde el inicio).
  • seek(offset, whence=0): Mueve el puntero del archivo a una nueva posición.
    • offset: Número de bytes a mover.
    • whence: Punto de referencia para el desplazamiento.
      • 0 (os.SEEK_SET): Desde el inicio del archivo (predeterminado).
      • 1 (os.SEEK_CUR): Desde la posición actual.
      • 2 (os.SEEK_END): Desde el final del archivo.
with open('ejemplo.txt', 'r+', encoding='utf-8') as archivo:
    print(f"Posición inicial: {archivo.tell()} bytes") # 0

    contenido = archivo.read(10) # Lee los primeros 10 caracteres
    print(f"Leído: '{contenido}'")
    print(f"Posición actual después de leer: {archivo.tell()} bytes") # 10 (aprox. si son caracteres ASCII)

    archivo.seek(0) # Vuelve al inicio
    print(f"Posición después de seek(0): {archivo.tell()} bytes") # 0

    archivo.seek(5, 0) # Mueve 5 bytes desde el inicio
    print(f"Posición después de seek(5, 0): {archivo.tell()} bytes")

    archivo.write("CAMBIO") # Sobrescribe desde la posición actual
    archivo.seek(0) # Vuelve al inicio para leer el archivo modificado
    print("--- Contenido modificado ---")
    print(archivo.read())

Serialización con pickle

El módulo pickle permite serializar (convertir objetos Python en un flujo de bytes) y deserializar (reconstruir objetos Python a partir de ese flujo) objetos complejos de Python. Esto es útil para almacenar objetos en archivos o transmitirlos a través de la red.

Guardar un Objeto (Serializar)

import pickle

data = {'nombre': 'Alice', 'edad': 30, 'ciudades': ['Nueva York', 'Londres']}

with open('data.pkl', 'wb') as archivo:
    pickle.dump(data, archivo)
print("Objeto serializado y guardado en 'data.pkl'.")

Cargar un Objeto (Deserializar)

import pickle

with open('data.pkl', 'rb') as archivo:
    loaded_data = pickle.load(archivo)
print("Objeto deserializado de 'data.pkl':")
print(loaded_data)
print(f"Tipo de objeto: {type(loaded_data)}")

Funciones y Ámbito de Variables

Las funciones son bloques de código reutilizables que realizan una tarea específica. Entender cómo definirlas, su ámbito y el manejo de valores de retorno es esencial.

Definición de Funciones

Las funciones se definen usando la palabra clave def, seguida del nombre de la función, paréntesis para los parámetros y un punto y coma.

def saludar(nombre):
    """Esta función imprime un saludo."""
    print(f"¡Hola, {nombre}!")

def sumar(a, b):
    """Esta función retorna la suma de dos números."""
    return a + b

# Llamadas a funciones
saludar("Carlos")
resultado_suma = sumar(5, 3)
print(f"La suma es: {resultado_suma}")

La cadena de texto (docstring) después de la definición de la función sirve como documentación y se puede acceder con help(nombre_funcion).

Ámbito de Variables

El ámbito (scope) de una varible determina dónde puede ser accedida o modificada. En Python, hay principalmente dos ámbitos: local y global.

Variables Locales

Definidas dentro de una función, solo son accesibles dentro de esa función.

def mi_funcion():
    variable_local = "Soy local"
    print(variable_local)

mi_funcion()
# print(variable_local) # Esto causaría un NameError

Variables Globales

Definidas fuera de cualquier función, son accesibles desde cualquier parte del programa. Para modificarlas dentro de una función, se debe usar la palabra clave global.

variable_global = "Soy global"

def otra_funcion():
    print(variable_global) # Acceso a la variable global
    
    # Intentar modificarla sin 'global' crearía una nueva variable local
    # variable_global = "Modificada localmente" # Esto no afectaría a la global

def modificar_global():
    global variable_global # Declara que estamos usando la variable global
    variable_global = "Soy global y he sido modificada"
    print(variable_global)

otra_funcion() # Imprime "Soy global"
modificar_global() # Imprime "Soy global y he sido modificada"
print(variable_global) # Imprime "Soy global y he sido modificada"

Valores de Retorno

La palabra clave return se usa para enviar un valor de vuelta desde una función. Si no se especifica return, la función devuelve implícitamente None.

def calcular_area(base, altura):
    area = 0.5 * base * altura
    return area

area_triangulo = calcular_area(10, 5)
print(f"El área del triángulo es: {area_triangulo}")

def no_retorna_nada():
    print("Esta función no usa return.")

resultado_none = no_retorna_nada()
print(f"El resultado de no_retorna_nada() es: {resultado_none}")

Funciones Especiales

(1) Funciones Lambda (Anónimas)

Son funciones pequeñas y anónimas que pueden tener cualquier número de argumentos, pero solo pueden tener una expresión. Se definen con la palabra clave lambda.

# Lambda para sumar dos números
sumar_lambda = lambda x, y: x + y
print(f"Suma con lambda: {sumar_lambda(4, 6)}")

# Lambda como argumento para otra función (ej. sorted)
lista_pares = [(1, 'b'), (3, 'a'), (2, 'c')]
lista_ordenada = sorted(lista_pares, key=lambda par: par[1]) # Ordena por el segundo elemento de la tupla
print(f"Lista ordenada por segundo elemento: {lista_ordenada}")

(2) map(): Aplicar una función a cada elemento

Aplica una función dada a cada elemento de un iterable y devuelve un iterador con los resultados. En Python 3, para ver los resultados como una lista, debe convertir el iterador explícitamente.

numeros = [1, 2, 3, 4, 5]

# Duplicar cada número
duplicados = list(map(lambda x: x * 2, numeros))
print(f"Números duplicados: {duplicados}")

# Convertir a cadena
cadenas = list(map(str, numeros))
print(f"Números como cadenas: {cadenas}")

(3) filter(): Filtrar elementos

Construye un iterador a partir de los elementos de un iterable para los que una función devuelve True.

edades = [12, 17, 18, 25, 15, 20]

# Filtrar edades mayores o iguales a 18
adultos = list(filter(lambda edad: edad >= 18, edades))
print(f"Edades de adultos: {adultos}")

(4) functools.reduce(): Aplicación acumulativa

Aplica una función a los elementos de un iterable de forma acumulativa, reduciéndolo a un solo valor. Requiere importar desde el módulo functools.

from functools import reduce

numeros = [1, 2, 3, 4, 5]

# Sumar todos los números (acumulativo)
suma_total = reduce(lambda x, y: x + y, numeros)
print(f"Suma total con reduce: {suma_total}")

# Encontrar el producto de todos los números
producto_total = reduce(lambda x, y: x * y, numeros)
print(f"Producto total con reduce: {producto_total}")

(5) yield: Generadores

La palabra clave yield se utiliza para definir funciones generadoras. Una función generadora es una función que, en lugar de devolver un solo valor con return, produce una secuencia de valores uno por uno, suspendiendo su ejecución entre cada valor.

def generador_cuadrados(n):
    for i in range(n):
        yield i * i # 'yield' produce un valor y pausa la ejecución

# Usar el generador
for cuadrado in generador_cuadrados(5):
    print(f"Cuadrado: {cuadrado}")

# También se puede convertir a lista si se necesita todos los valores de una vez
lista_cuadrados = list(generador_cuadrados(3))
print(f"Lista de cuadrados: {lista_cuadrados}")

Estructuras de Control de Flujo

Las estructuras de control de flujo permiten dictar el orden en que se ejecutan las instrucciones en un programa, posibilitando la toma de decisiones y la repetición de tareas.

Sentencias Condicionales: if, elif, else

Permiten ejecutar bloques de código específicos basándose en si una condición es verdadera o falsa.

puntuacion = 75

if puntuacion >= 90:
    print("Calificación: A")
elif puntuacion >= 80:
    print("Calificación: B")
elif puntuacion >= 70:
    print("Calificación: C")
else:
    print("Calificación: D")

Bucle for

El bucle for se usa para iterar sobre los elementos de una secuencia (lista, tupla, cadena, etc.) o cualquier otro objeto iterable.

frutas = ["manzana", "banana", "cereza"]
for fruta in frutas:
    print(f"Me gusta la {fruta}.")

print("\n--- Con range() ---")
for i in range(3): # Itera 3 veces (0, 1, 2)
    print(f"Iteración número {i}")

Bucle while

El bucle while ejecuta un bloque de código repetidamente siempre y cuando una condición sea verdadera. Es importante asegurarse de que la condición eventualmente se vuelva falsa para evitar bucles infinitos.

contador = 0
while contador < 3:
    print(f"Contador: {contador}")
    contador += 1 # Incrementa el contador para que la condición eventualmente sea falsa

Control de Bucle: continue y break

  • break: Termina el bucle actual completamente y salta a la instrucción que sigue inmediatamente al bucle.
  • continue: Salta el resto de la iteración actual y pasa a la siguiente iteración del bucle.
print("--- Ejemplo con break ---")
for i in range(10):
    if i == 5:
        break # El bucle se detiene cuando i es 5
    print(i)

print("\n--- Ejemplo con continue ---")
for i in range(10):
    if i % 2 == 0: # Si i es par
        continue # Salta esta iteración, no imprime el número par
    print(i) # Solo imprime números impares

Tipos de Datos Secuenciales

Las secuencias son colecciones ordenadas de elementos. Python ofrece varios tipos de datos secuenciales, siendo las cadenas (str), listas (list) y tuplas (tuple) los más comunes.

Propiedades Comunes de las Secuencias

  • Ordenadas: Los elementos tienen una posición definida (índice).
  • Indexables: Se puede acceder a los elementos por su índice (secuencia[indice]).
  • Cortables (Slicing): Se pueden extraer sub-secuencias (secuencia[inicio:fin:paso]).

Operaciones Genéricas de Secuencia

mi_lista = [10, 20, 30, 40, 50]
mi_cadena = "Python"
mi_tupla = (1, 2, 3)

# Acceso por índice
print(f"Elemento en índice 1 de la lista: {mi_lista[1]}") # 20
print(f"Carácter en índice 0 de la cadena: {mi_cadena[0]}") # P

# Slicing (corte)
print(f"Sub-lista: {mi_lista[1:4]}") # [20, 30, 40]
print(f"Sub-cadena: {mi_cadena[2:]}") # thon

# Concatenación (+)
otra_lista = [60, 70]
print(f"Listas concatenadas: {mi_lista + otra_lista}")

# Repetición (*)
print(f"Cadena repetida: {mi_cadena * 2}") # PythonPython

# Pertenencia (in, not in)
print(f"¿30 está en mi_lista? {'30' in mi_lista}") # False (tipos diferentes)
print(f"¿'y' está en mi_cadena? {'y' in mi_cadena}") # True

Cadenas (str)

Las cadenas son secuencias inmutables de caracteres. Una vez creadas, no se pueden modificar.

Métodos Comunes de Cadenas

texto = "  Hola Mundo PYTHON!  "

print(f"Original: '{texto}'")
print(f"Longitud: {len(texto)}")

# Modificación de mayúsculas/minúsculas
print(f"Minúsculas: '{texto.lower()}'")
print(f"Mayúsculas: '{texto.upper()}'")
print(f"Capitalizar: '{texto.capitalize()}'") # Primera letra de toda la cadena
print(f"Título: '{texto.title()}'") # Primera letra de cada palabra

# Eliminar espacios en blanco
print(f"Sin espacios al inicio/fin: '{texto.strip()}'")
print(f"Sin espacios a la izquierda: '{texto.lstrip()}'")
print(f"Sin espacios a la derecha: '{texto.rstrip()}'")

# Buscar y reemplazar
print(f"Índice de 'Mundo': {texto.find('Mundo')}") # Retorna -1 si no encuentra
print(f"Contar 'o': {texto.count('o')}")
print(f"Reemplazar 'Mundo' por 'Amigo': '{texto.replace('Mundo', 'Amigo')}'")

# Dividir y Unir
frase = "Python es divertido y potente"
palabras = frase.split(' ')
print(f"Dividir por espacio: {palabras}")
nueva_frase = '-'.join(palabras)
print(f"Unir con guión: {nueva_frase}")

# Verificación
print(f"¿Termina con '!'? {texto.strip().endswith('!')}")
print(f"¿Empieza con 'Hola'? {texto.strip().startswith('Hola')}")

Listas (list)

Las listas son secuencias mutables (se pueden modificar después de su creación) de elementos, que pueden ser de diferentes tipos.

Métodos Comunes de Listas

mis_numeros = [3, 1, 4, 1, 5, 9, 2]
print(f"Original: {mis_numeros}")

# Añadir elementos
mis_numeros.append(6) # Añade al final
print(f"Después de append(6): {mis_numeros}")
mis_numeros.insert(2, 7) # Inserta 7 en índice 2
print(f"Después de insert(2, 7): {mis_numeros}")

# Extender con otra secuencia
otras_cifras = [8, 0]
mis_numeros.extend(otras_cifras)
print(f"Después de extend([8, 0]): {mis_numeros}")

# Eliminar elementos
mis_numeros.remove(1) # Elimina la primera ocurrencia de 1
print(f"Después de remove(1): {mis_numeros}")
elemento_eliminado = mis_numeros.pop(0) # Elimina y devuelve el elemento en índice 0
print(f"Después de pop(0): {mis_numeros}, Eliminado: {elemento_eliminado}")

# Ordenar y Revertir
mis_numeros.sort() # Ordena la lista in-place
print(f"Después de sort(): {mis_numeros}")
mis_numeros.reverse() # Invierte el orden in-place
print(f"Después de reverse(): {mis_numeros}")

# Contar y Encontrar
print(f"Número de '1's: {mis_numeros.count(1)}")
print(f"Índice del primer '5': {mis_numeros.index(5)}")

# Copiar listas (Copia Superficial vs Profunda)
lista_original = [1, 2, ['a', 'b']]
lista_copia_superficial = list(lista_original) # o lista_original[:]
lista_copia_profunda = copy.deepcopy(lista_original) # Requiere import copy

lista_original.append(3)
lista_original[2].append('c') # Esto afecta a la copia superficial

print(f"Original: {lista_original}")
print(f"Copia Superficial: {lista_copia_superficial}") # Contendrá 'c'
print(f"Copia Profunda: {lista_copia_profunda}") # No contendrá 'c'

Tuplas (tuple)

Las tuplas son secuencias inmutables de elementos. Son similares a las listas, pero no se pueden modificar una vez creadas.

Definición y Acceso

mi_tupla = (10, "Python", 3.14, True)
otra_tupla = 20, "Java" # Los paréntesis son opcionales para la creación

print(f"Tupla: {mi_tupla}")
print(f"Elemento en índice 1: {mi_tupla[1]}") # Python

Empaquetado y Desempaquetado de Tuplas

# Empaquetado: asignar múltiples valores a una tupla
coordenadas = 10, 20
print(f"Coordenadas empaquetadas: {coordenadas}") # (10, 20)

# Desempaquetado: asignar elementos de una tupla a variables individuales
x, y = coordenadas
print(f"x: {x}, y: {y}") # x: 10, y: 20

# Útil para retornar múltiples valores de una función
def obtener_info_usuario():
    return "Ana", 28, "anal@example.com"

nombre, edad, email = obtener_info_usuario()
print(f"Nombre: {nombre}, Edad: {edad}, Email: {email}")

Funciones Integradas Útiles para Secuencias

  • len(secuencia): Retorna la longitud de la secuencia.
  • max(secuencia): Retorna el elemento más grande.
  • min(secuencia): Retorna el elemento más pequeño.
  • sum(iterable, start=0): Retorna la suma de los elementos de un iterable.
  • enumerate(iterable, start=0): Devuelve un iterador que produce tuplas (índice, valor).
  • zip(iterable1, iterable2, ...): Combina elementos de múltiples iterables en tuplas.
  • sorted(iterable, key=None, reverse=False): Devuelve una nueva lista ordenada a partir de los elementos del iterable. No modifica el original.
  • reversed(secuencia): Devuelve un iterador que produce elementos en orden inverso. No modifica el original.
nombres = ["Elena", "David", "Carlos", "Beatriz", "Ana"]
puntuaciones = [85, 92, 78, 95, 88]

# enumerate
print("--- Enumerar nombres ---")
for indice, nombre in enumerate(nombres):
    print(f"{indice+1}. {nombre}")

# zip
print("\n--- Combinar con zip ---")
for nombre, puntuacion in zip(nombres, puntuaciones):
    print(f"{nombre}: {puntuacion} puntos")

# sorted
print(f"\nLista de nombres original: {nombres}")
nombres_ordenados = sorted(nombres)
print(f"Nombres ordenados (nueva lista): {nombres_ordenados}")

# sorted con una clave personalizada
# Ordenar la lista de nombres por su longitud
nombres_por_longitud = sorted(nombres, key=len)
print(f"Nombres ordenados por longitud: {nombres_por_longitud}")

# sorted con clave personalizada para una lista de tuplas (nombre, puntuacion)
alumnos_puntuaciones = list(zip(nombres, puntuaciones))
print(f"Alumnos y puntuaciones: {alumnos_puntuaciones}")
# Ordenar por puntuación (segundo elemento de cada tupla)
alumnos_ordenados_por_puntuacion = sorted(alumnos_puntuaciones, key=lambda item: item[1], reverse=True)
print(f"Alumnos ordenados por puntuación (descendente): {alumnos_ordenados_por_puntuacion}")

# reversed
print("\n--- Nombres en orden inverso (iterador) ---")
for nombre_inverso in reversed(nombres):
    print(nombre_inverso)

# sum, max, min
print(f"\nPuntuación máxima: {max(puntuaciones)}")
print(f"Puntuación mínima: {min(puntuaciones)}")
print(f"Suma total de puntuaciones: {sum(puntuaciones)}")

Diccionarios (dict)

Los diccionarios son colecciones desordenadas (en versiones anteriores a Python 3.7, ahora mantienen el orden de inserción) de pares clave-valor. Las claves deben ser únicas e inmutables (cadenas, números, tuplas), mientras que los valores pueden ser de cualquier tipo.

Creación y Acceso

# Crear un diccionario
estudiante = {
    "nombre": "Elena",
    "edad": 22,
    "carrera": "Ingeniería Informática",
    "cursos": ["Programación", "Bases de Datos", "Redes"]
}

print(f"Estudiante: {estudiante}")

# Acceder a un valor por su clave
print(f"Nombre del estudiante: {estudiante['nombre']}")
print(f"Edad del estudiante: {estudiante['edad']}")

# Intentar acceder a una clave inexistente causaría un KeyError
# print(estudiante['direccion']) # Error

Modificación y Adición

# Añadir una nueva clave-valor
estudiante["universidad"] = "Universidad XYZ"
print(f"Después de añadir universidad: {estudiante}")

# Modificar un valor existente
estudiante["edad"] = 23
print(f"Después de actualizar edad: {estudiante}")

Eliminación de Elementos

# Eliminar con 'del'
del estudiante["carrera"]
print(f"Después de eliminar carrera: {estudiante}")

# Eliminar con 'pop()' (devuelve el valor eliminado)
cursos_eliminados = estudiante.pop("cursos")
print(f"Después de eliminar cursos con pop(): {estudiante}, Cursos eliminados: {cursos_eliminados}")

Método .get() para Acceso Seguro

El método .get() permite acceder a un valor por su clave de forma segura. Si la clave no existe, devuelve None o un valor predeterminado especificado.

print(f"Obtener nombre con .get(): {estudiante.get('nombre')}")
print(f"Obtener dirección (no existe): {estudiante.get('direccion')}") # None
print(f"Obtener dirección (con valor por defecto): {estudiante.get('direccion', 'Desconocida')}")

Iteración sobre Diccionarios

# Iterar sobre las claves (predeterminado)
print("--- Claves ---")
for clave in estudiante:
    print(clave)

# Iterar sobre los valores
print("\n--- Valores ---")
for valor in estudiante.values():
    print(valor)

# Iterar sobre pares clave-valor (recomendado)
print("\n--- Claves y Valores ---")
for clave, valor in estudiante.items():
    print(f"{clave}: {valor}")

Conjuntos (set)

Los conjuntos son colecciones dseordenadas de elementos únicos. Son mutables y no permiten elementos duplicados. Son ideales para operaciones matemáticas de conjuntos como unión, intersección y diferencia.

Creación de Conjuntos

# Crear un conjunto a partir de una lista (elimina duplicados automáticamente)
lista_numeros = [1, 2, 2, 3, 4, 4, 5]
mi_conjunto = set(lista_numeros)
print(f"Conjunto desde lista: {mi_conjunto}") # {1, 2, 3, 4, 5} (orden puede variar)

# Crear un conjunto vacío
conjunto_vacio = set()
print(f"Conjunto vacío: {conjunto_vacio}")

# Crear un conjunto directamente con llaves (¡cuidado con el diccionario vacío!)
conjunto_directo = {6, 7, 8}
print(f"Conjunto directo: {conjunto_directo}")

Añadir y Eliminar Elementos

mi_conjunto.add(6) # Añade un elemento
print(f"Después de add(6): {mi_conjunto}")
mi_conjunto.add(2) # Intentar añadir un duplicado no tiene efecto
print(f"Después de add(2) (sin cambios): {mi_conjunto}")

mi_conjunto.discard(3) # Elimina 3 si existe, no da error si no existe
print(f"Después de discard(3): {mi_conjunto}")

# mi_conjunto.remove(10) # Esto daría un KeyError si 10 no está en el conjunto
mi_conjunto.pop() # Elimina y devuelve un elemento arbitrario
print(f"Después de pop(): {mi_conjunto}")

mi_conjunto.clear() # Elimina todos los elementos
print(f"Después de clear(): {mi_conjunto}")

Operaciones de Conjuntos

set_a = {1, 2, 3, 4, 5}
set_b = {4, 5, 6, 7, 8}

# Unión
union_sets = set_a.union(set_b)
print(f"Unión de A y B: {union_sets}") # {1, 2, 3, 4, 5, 6, 7, 8}

# Intersección
interseccion_sets = set_a.intersection(set_b)
print(f"Intersección de A y B: {interseccion_sets}") # {4, 5}

# Diferencia (elementos en A pero no en B)
diferencia_sets = set_a.difference(set_b)
print(f"Diferencia A - B: {diferencia_sets}") # {1, 2, 3}

# Diferencia simétrica (elementos en A o B, pero no en ambos)
simetrica_sets = set_a.symmetric_difference(set_b)
print(f"Diferencia simétrica de A y B: {simetrica_sets}") # {1, 2, 3, 6, 7, 8}

# Actualizar un conjunto con otra operación
set_a.update(set_b) # set_a ahora es la unión de set_a y set_b
print(f"Set A después de update(Set B): {set_a}")

# Verificar subconjunto/superconjunto
set_c = {1, 2}
print(f"¿Set C es subconjunto de Set A? {set_c.issubset(set_a)}")
print(f"¿Set A es superconjunto de Set C? {set_a.issuperset(set_c)}")

# Verificar disyunción (no tienen elementos en común)
set_d = {9, 10}
print(f"¿Set A y Set D son disjuntos? {set_a.isdisjoint(set_d)}")

Programación Orientada a Objetos: Módulos, Clases y Objetos

La Programación Orientada a Objetos (POO) es un paradigma de programación que utiliza "objetos" para diseñar aplicaciones y programas informáticos. Python es un lenguaje multiparadigma, con un fuerte soporte para la POO.

Módulos vs. Clases

  • Módulo: Es simplemente un archivo .py que contiene código Python (variables, funciones, clases). Sirve para organizar el código en archivos lógicos y reutilizables. Se importa con import nombre_modulo. ```python

    mi_utilidades.py

    def saludar(nombre): return f"¡Hola, {nombre} desde el módulo!"

    CONSTANTE = 123

    En otro archivo.py

    import mi_utilidades print(mi_utilidades.saludar("Juan")) print(mi_utilidades.CONSTANTE)

  • Clase: Es una plantilla o "plano" para crear objetos. Define un conjunto de atributos (variables) y métodos (funciones) que los objetos de esa clase tendrán. Una clase encapsula datos y comportamiento.

Clases y Objetos

Un objeto es una instancia de una clase. La clase define la estructura y el comportamiento, y el objeto es una entidad concreta creada a partir de esa estructura.

Definición de Clases

Las clases se definen usando la palabra clave class.

class Gato:
    # Atributo de clase: compartido por todas las instancias
    especie = "felino"

    def __init__(self, nombre, edad):
        # Método constructor: se llama al crear una nueva instancia
        # 'self' se refiere a la instancia actual del objeto
        self.nombre = nombre # Atributo de instancia
        self.edad = edad     # Atributo de instancia
        print(f"Gato {self.nombre} creado.")

    def maullar(self):
        """Método de instancia: comportamiento del objeto."""
        return f"{self.nombre} dice: ¡Miau!"

    def describir(self):
        """Docstring para el método describir."""
        return f"Soy {self.nombre}, un {self.especie} de {self.edad} años."

# Instanciación de objetos (creación de objetos a partir de la clase)
gato1 = Gato("Garfield", 5)
gato2 = Gato("Tom", 3)

# Acceso a atributos y métodos
print(f"{gato1.nombre} tiene {gato1.edad} años.")
print(gato2.maullar())
print(gato1.describir())

# Acceso al atributo de clase
print(f"Todos los gatos son de la especie: {Gato.especie}")
print(f"{gato1.nombre} es de la especie: {gato1.especie}")

# Se puede obtener ayuda sobre la clase y sus métodos
# help(Gato)
# help(Gato.maullar)

El Parámetro self

En Python, los métodos de clase deben tener un primer parámetro llamado self (por convención, podría ser cualquier nombre). self es una referencia a la instancia del objeto que está llamando al método. A través de self, un método puede acceder a los atributos y otros métodos de esa misma instancia.

Herencia

La herencia permite que una clase (subclase o clase hija) adquiera los atributos y métodos de otra clase (superclase o clase padre). Esto promueve la reutilización de código.

class Animal:
    def __init__(self, nombre):
        self.nombre = nombre
    
    def respirar(self):
        return f"{self.nombre} está respirando."

class Mamifero(Animal):
    def __init__(self, nombre, tipo_pelaje):
        super().__init__(nombre) # Llama al constructor de la clase padre
        self.tipo_pelaje = tipo_pelaje

    def amamantar(self):
        return f"{self.nombre} está amamantando."

class Perro(Mamifero):
    def __init__(self, nombre, tipo_pelaje, raza):
        super().__init__(nombre, tipo_pelaje)
        self.raza = raza

    def ladrar(self):
        return f"{self.nombre} dice: ¡Guau, guau!"

# Crear instancias de las clases
animal = Animal("Ser vivo")
mamifero = Mamifero("León", "melena")
perro = Perro("Fido", "corto", "Labrador")

print(animal.respirar())
print(mamifero.respirar())
print(mamifero.amamantar())
print(perro.respirar())
print(perro.amamantar())
print(perro.ladrar())
print(f"{perro.nombre} es de raza {perro.raza} y tiene pelaje {perro.tipo_pelaje}.")

En el ejemplo anterior, Perro es un tipo de Mamifero (relación "es-un"), y Mamifero es un tipo de Animal. Esto significa que un Perro hereda todas las funcionalidades de Mamifero y Animal.

Atributos de Clase y Atributos de Instancia

  • Atributos de Clase: Son compartidos por todas las instancias de la clase. Se definen directamente dentro de la clase, pero fuera de cualquier método.
  • Atributos de Instancia: Son únicos para cada instancia del objeto. Se definen dentro del método __init__ usando self.nombre_atributo.

Composición

La composición es otra forma de reutilizar código, donde una clase contiene una instancia de otra clase como uno de sus atributos (relación "tiene-un").

class Motor:
    def __init__(self, tipo):
        self.tipo = tipo
    
    def arrancar(self):
        return f"Motor {self.tipo} arrancado."

class Coche:
    def __init__(self, marca, modelo, tipo_motor):
        self.marca = marca
        self.modelo = modelo
        self.motor = Motor(tipo_motor) # El coche 'tiene-un' motor

    def conducir(self):
        return f"El {self.marca} {self.modelo} está conduciendo. {self.motor.arrancar()}"

mi_coche = Coche("Toyota", "Corolla", "gasolina")
print(mi_coche.conducir())

Aquí, la clase Coche tiene una instancia de la clase Motor, lo que demuestra la composición.

Etiquetas: Python Python3 Programación Orientada a Objetos POO Módulos

Publicado el 5-31 14:05