0% encontró este documento útil (0 votos)
43 vistas14 páginas

Material de Apoyo Python - Listas-Dic-Conjuntos

El documento presenta una guía sobre el uso de estructuras de datos en Python, incluyendo listas, tuplas, diccionarios, conjuntos y arrays utilizando NumPy. Se explican operaciones comunes y funciones relacionadas con cada tipo de estructura, así como conceptos de programación orientada a objetos como encapsulación, herencia y métodos abstractos. Además, se incluyen ejemplos prácticos y consejos para mejorar la mantenibilidad del código mediante el uso de type hints.
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
43 vistas14 páginas

Material de Apoyo Python - Listas-Dic-Conjuntos

El documento presenta una guía sobre el uso de estructuras de datos en Python, incluyendo listas, tuplas, diccionarios, conjuntos y arrays utilizando NumPy. Se explican operaciones comunes y funciones relacionadas con cada tipo de estructura, así como conceptos de programación orientada a objetos como encapsulación, herencia y métodos abstractos. Además, se incluyen ejemplos prácticos y consejos para mejorar la mantenibilidad del código mediante el uso de type hints.
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF, TXT o lee en línea desde Scribd
Está en la página 1/ 14

Repositorio: h,ps://github.com/arlemorales27/python_Arrays_dict_clase_crud.

git

1. Listas (List)

from typing import List, Any

# Lista con tipo específico (solo enteros)


numeros: List[int] = [1, 2, 3, 4, 5]

# Lista que puede contener diferentes tipos


datos_mixtos: List[Any] = [1, "hola", 3.14, True]

# Operaciones comunes con listas tipadas


def suma_lista(numeros: List[int]) -> int:
return sum(numeros)

def agregar_elemento(lista: List[str], elemento: str) -> None:


lista.append(elemento)

# Ejemplo de uso
nombres: List[str] = ["Ana", "Juan"]
agregar_elemento(nombres, "Carlos")
print(nombres) # Output: ['Ana', 'Juan', 'Carlos']

2. Tuplas (Tuple)

from typing import Tuple

# Tupla con tipos específicos


coordenadas: Tuple[float, float] = (10.5, 20.7)

# Tupla con diferentes tipos


usuario: Tuple[int, str, bool] = (1, "Juan Pérez", True)

# Función que retorna múltiples valores tipados


def obtener_datos_usuario() -> Tuple[str, int, str]:
return ("Juan", 25, "[email protected]")

nombre, edad, email = obtener_datos_usuario()

3. Diccionarios (Dict)

from typing import Dict, Union

# Diccionario con tipos específicos


usuarios: Dict[str, str] = {
"usuario1": "Juan",
"usuario2": "María"
}

# Diccionario con valores de diferentes tipos


perfil_usuario: Dict[str, Union[str, int, bool]] = {
"nombre": "Ana",
"edad": 25,
"activo": True
}

# Función que procesa un diccionario tipado


def obtener_edad(usuario: Dict[str, Union[str, int, bool]]) -> int:
return int(usuario["edad"])

edad_usuario = obtener_edad(perfil_usuario)

4. Conjuntos (Set)

from typing import Set

# Conjunto de elementos únicos tipados


numeros_unicos: Set[int] = {1, 2, 3, 4, 5}

# Función que trabaja con conjuntos


def unir_conjuntos(conjunto1: Set[str], conjunto2: Set[str]) -> Set[str]:
return conjunto1.union(conjunto2)

nombres1: Set[str] = {"Ana", "Juan"}


nombres2: Set[str] = {"María", "Pedro"}
todos_nombres = unir_conjuntos(nombres1, nombres2)

5. Arrays (usando NumPy para arrays más eficientes)

import numpy as np
from typing import List, Any
from numpy.typing import NDArray

# Array tipado de NumPy


numeros_array: NDArray[np.int64] = np.array([1, 2, 3, 4, 5])

# Función que procesa arrays NumPy


def calcular_promedio(arr: NDArray[np.float64]) -> float:
return float(np.mean(arr))

datos: NDArray[np.float64] = np.array([1.5, 2.7, 3.2, 4.8])


promedio = calcular_promedio(datos)

Funciones especiales
from typing import List, Any, Optional
def demostrar_operaciones_listas() -> None:
# Creamos una lista inicial
frutas: List[str] = ["manzana", "banana", "naranja"]

# 1. Agregar elementos (similar a push en otros lenguajes)


frutas.append("pera") # Agrega al final
print(f"Después de append: {frutas}")
# Output: ['manzana', 'banana', 'naranja', 'pera']

# 2. Insertar en una posición específica


frutas.insert(1, "uva") # Inserta en el índice 1
print(f"Después de insert: {frutas}")
# Output: ['manzana', 'uva', 'banana', 'naranja', 'pera']

# 3. Extender la lista con otra lista


mas_frutas: List[str] = ["mango", "papaya"]
frutas.extend(mas_frutas)
print(f"Después de extend: {frutas}")
# Output: ['manzana', 'uva', 'banana', 'naranja', 'pera', 'mango',
'papaya']

# 4. Eliminar elementos
# Por valor
frutas.remove("uva")
print(f"Después de remove: {frutas}")

# Por índice
fruta_eliminada: str = frutas.pop(2) # Elimina y retorna el elemento
print(f"Elemento eliminado: {fruta_eliminada}")
print(f"Después de pop: {frutas}")

# Eliminar último elemento


ultima_fruta: str = frutas.pop() # Sin índice elimina el último

# 5. Buscar elementos
indice_naranja: int = frutas.index("naranja")
print(f"Índice de naranja: {indice_naranja}")

# 6. Contar ocurrencias
frutas.append("manzana")
cantidad_manzanas: int = frutas.count("manzana")
print(f"Cantidad de manzanas: {cantidad_manzanas}")

# 7. Ordenar la lista
frutas.sort() # Ordena la lista original
print(f"Lista ordenada: {frutas}")

frutas.sort(reverse=True) # Orden descendente


print(f"Lista ordenada descendente: {frutas}")
# 8. Invertir la lista
frutas.reverse()
print(f"Lista invertida: {frutas}")

# 9. Copiar una lista


nueva_lista: List[str] = frutas.copy()

# 10. Limpiar la lista


frutas.clear()
print(f"Lista después de clear: {frutas}")

# Ejecutamos la demostración
demostrar_operaciones_listas()

Ahora veamos los Diccionarios:

from typing import Dict, Union, List, Optional

def demostrar_operaciones_diccionarios() -> None:


# Creamos un diccionario inicial
estudiante: Dict[str, Union[str, int, List[float]]] = {
"nombre": "Ana",
"edad": 20,
"notas": [8.5, 9.0, 7.5]
}

# 1. Agregar o modificar elementos


estudiante["carrera"] = "Informática" # Agregar nuevo par clave-
valor
estudiante["edad"] = 21 # Modificar valor existente

# 2. Obtener valores
# Con get (manera segura, permite valor por defecto)
nombre: str = estudiante.get("nombre", "Sin nombre")
# Si la clave no existe, retorna el valor por defecto
telefono: str = estudiante.get("telefono", "No disponible")

# 3. Obtener todas las claves y valores


claves: List[str] = list(estudiante.keys())
valores: List[Union[str, int, List[float]]] =
list(estudiante.values())
items: List[tuple] = list(estudiante.items())

# 4. Verificar si existe una clave


tiene_nombre: bool = "nombre" in estudiante

# 5. Eliminar elementos
# Pop - Elimina y retorna el valor
edad_eliminada: int = estudiante.pop("edad")

# Popitem - Elimina y retorna el último par insertado


ultimo_par: tuple = estudiante.popitem()

# Del - Elimina directamente


if "notas" in estudiante:
del estudiante["notas"]

# 6. Actualizar el diccionario
nuevos_datos: Dict[str, Union[str, int]] = {
"semestre": 3,
"estado": "activo"
}
estudiante.update(nuevos_datos)

# 7. Obtener valor con manejo de error


try:
nota_media: float = sum(estudiante.get("notas", [0])) /
len(estudiante.get("notas", [1]))
except ZeroDivisionError:
nota_media = 0.0

# 8. Crear un diccionario a partir de listas


claves_nuevas: List[str] = ["a", "b", "c"]
valores_nuevos: List[int] = [1, 2, 3]
nuevo_dict: Dict[str, int] = dict(zip(claves_nuevas, valores_nuevos))

# 9. Diccionario por comprensión


cuadrados: Dict[int, int] = {x: x**2 for x in range(5)}

# 10. Fusionar diccionarios (Python 3.9+)


dict1: Dict[str, int] = {"a": 1, "b": 2}
dict2: Dict[str, int] = {"c": 3, "d": 4}
dict_combinado: Dict[str, int] = dict1 | dict2

# Imprimir resultados para demostración


print(f"Estudiante final: {estudiante}")
print(f"Nuevo diccionario: {nuevo_dict}")
print(f"Cuadrados: {cuadrados}")
print(f"Diccionarios combinados: {dict_combinado}")

# Ejecutamos la demostración
demostrar_operaciones_diccionarios()

Algunos puntos importantes a destacar:

1. En listas:
o append() es el equivalente a push() en otros lenguajes
o pop() puede eliminar y retornar tanto el último elemento como uno
específico
o Hay múltiples formas de eliminar elementos: remove(), pop(), del
2. En diccionarios:
o Usar get() es más seguro que acceso directo con corchetes
o update() es muy útil para combinar diccionarios
o Los diccionarios son muy flexibles en cuanto a los tipos de valores que
pueden almacenar
3. Tips generales:
o Siempre verifica la existencia de claves en diccionarios antes de acceder
o Usa type hints para hacer el código más mantenible

from typing import Optional, List


from datetime import date

class Empleado:
# Variable de clase (compartida por todas las instancias)
empresa: str = "TechCorp"

# Constructor
def __init__(self, nombre: str, salario: float) -> None:
# Atributos públicos (accesibles desde cualquier parte)
self.nombre: str = nombre

# Atributos protegidos (convención de un guion bajo)


# Se sugiere no acceder directamente desde fuera de la clase
self._departamento: str = "Sin asignar"

# Atributos privados (doble guion bajo)


# Python hace name mangling para dificultar el acceso directo
self.__salario: float = salario
self.__fecha_ingreso: date = date.today()

# Lista para almacenar el historial de roles


self.__historial_roles: List[str] = []

# Property getter - Obtener el salario


@property
def salario(self) -> float:
"""Obtiene el salario del empleado"""
return self.__salario

# Property setter - Establecer el salario


@salario.setter
def salario(self, nuevo_salario: float) -> None:
"""Establece el salario del empleado con validación"""
if nuevo_salario < 0:
raise ValueError("El salario no puede ser negativo")
self.__salario = nuevo_salario

# Método público
def asignar_departamento(self, departamento: str) -> None:
"""Asigna un departamento al empleado"""
self._departamento = departamento

# Método protegido
def _calcular_bonus(self) -> float:
"""Calcula el bonus basado en el salario"""
return self.__salario * 0.1

# Método privado
def __registrar_cambio(self, nuevo_rol: str) -> None:
"""Registra internamente un cambio de rol"""
self.__historial_roles.append(nuevo_rol)

# Método público que usa métodos privados y protegidos


def promocionar(self, nuevo_rol: str) -> None:
"""Promociona al empleado a un nuevo rol"""
self.__registrar_cambio(nuevo_rol)
bonus = self._calcular_bonus()
self.__salario += bonus
print(f"{self.nombre} ha sido promocionado a {nuevo_rol}")

# Método estático (no necesita acceso a la instancia)


@staticmethod
def verificar_dni(dni: str) -> bool:
"""Verifica si un DNI es válido"""
return len(dni) == 8 and dni.isdigit()

# Método de clase (tiene acceso a la clase pero no a la instancia)


@classmethod
def cambiar_empresa(cls, nueva_empresa: str) -> None:
"""Cambia el nombre de la empresa para todos los empleados"""
cls.empresa = nueva_empresa

def __str__(self) -> str:


"""Representación en string del empleado"""
return f"Empleado: {self.nombre}, Departamento:
{self._departamento}"

Ahora veamos cómo usar esta clase:

# Creación de instancias
empleado1 = Empleado("Ana García", 30000.0)
empleado2 = Empleado("Juan Pérez", 35000.0)

# Uso de métodos públicos


empleado1.asignar_departamento("Desarrollo")
print(empleado1) # Output: Empleado: Ana García, Departamento:
Desarrollo

# Uso de properties (getters y setters)


print(empleado1.salario) # Usa el getter
empleado1.salario = 32000.0 # Usa el setter

# Uso de método estático


dni_valido = Empleado.verificar_dni("12345678")

# Uso de método de clase


Empleado.cambiar_empresa("NewCorp")
print(empleado1.empresa) # Output: NewCorp
print(empleado2.empresa) # Output: NewCorp

# Promocionar empleado (usa métodos privados y protegidos internamente)


empleado1.promocionar("Tech Lead")

Vamos a crear una clase heredada para mostrar la herencia:

class Gerente(Empleado):
def __init__(self, nombre: str, salario: float, equipo:
Optional[List[Empleado]] = None) -> None:
# Llamamos al constructor de la clase padre
super().__init__(nombre, salario)

# Atributos específicos de Gerente


self.equipo: List[Empleado] = equipo or []

# Sobrescribimos el método de la clase padre


def _calcular_bonus(self) -> float:
"""Sobrescribe el cálculo de bonus para gerentes"""
# Los gerentes reciben un bonus mayor
return self.salario * 0.2

def agregar_miembro_equipo(self, empleado: Empleado) -> None:


"""Agrega un miembro al equipo del gerente"""
self.equipo.append(empleado)

Aspectos importantes a destacar:

1. Encapsulación:
o Atributos públicos: nombre
o Atributos protegidos: _departamento
o Atributos privados: __salario, __fecha_ingreso
2. Propiedades (Properties):
o Permiten controlar el acceso a atributos
o Pueden incluir validación
o Mantienen la encapsulación
3. Métodos especiales:
o __init__: Constructor
o __str__: Representación en string
o Pueden ser públicos, protegidos o privados
4. Decoradores:
o @property: Para getters
o @x.setter: Para setters
o @staticmethod: Para métodos estáticos
o @classmethod: Para métodos de clase
5. Herencia:
o La clase Gerente hereda de Empleado
o Usa super() para llamar al constructor padre
o Puede sobrescribir métodos del padre

Ejemplo de uso de la herencia:

# Crear un gerente
gerente = Gerente("Carlos López", 50000.0)

# Agregar empleados a su equipo


gerente.agregar_miembro_equipo(empleado1)
gerente.agregar_miembro_equipo(empleado2)

# El gerente tiene acceso a los métodos de Empleado


gerente.asignar_departamento("Dirección")

# Pero su bonus se calcula diferente (polimorfismo)


gerente.promocionar("Director de Área")

Métodos abstractos
from abc import ABC, abstractmethod
from typing import List, Optional

# Clase abstracta base que define una interfaz para figuras geométricas
class FiguraGeometrica(ABC):
def __init__(self, color: str) -> None:
self.color = color

# Método abstracto que DEBE ser implementado por las clases hijas
@abstractmethod
def calcular_area(self) -> float:
"""Calcula el área de la figura geométrica"""
pass

# Método abstracto que DEBE ser implementado por las clases hijas
@abstractmethod
def calcular_perimetro(self) -> float:
"""Calcula el perímetro de la figura geométrica"""
pass

# Método normal (no abstracto) que pueden usar todas las clases hijas
def describir(self) -> str:
"""Retorna una descripción de la figura"""
return f"Soy una figura de color {self.color}"

# Implementación concreta de la clase abstracta


class Rectangulo(FiguraGeometrica):
def __init__(self, color: str, base: float, altura: float) -> None:
# Llamamos al constructor de la clase padre
super().__init__(color)
self.base = base
self.altura = altura

# Implementación del método abstracto


def calcular_area(self) -> float:
return self.base * self.altura

# Implementación del método abstracto


def calcular_perimetro(self) -> float:
return 2 * (self.base + self.altura)

# Otra implementación concreta


class Circulo(FiguraGeometrica):
def __init__(self, color: str, radio: float) -> None:
super().__init__(color)
self.radio = radio

def calcular_area(self) -> float:


return 3.14159 * self.radio ** 2

def calcular_perimetro(self) -> float:


return 2 * 3.14159 * self.radio

# Sistema que utiliza las figuras geométricas


class SistemaGeometrico:
def __init__(self) -> None:
self.figuras: List[FiguraGeometrica] = []

def agregar_figura(self, figura: FiguraGeometrica) -> None:


"""
Agrega una figura al sistema.
Nota cómo el tipo es FiguraGeometrica, permitiendo cualquier
subclase
"""
self.figuras.append(figura)

def calcular_area_total(self) -> float:


"""Calcula el área total de todas las figuras"""
return sum(figura.calcular_area() for figura in self.figuras)

# Ejemplo de uso
def demostrar_sistema_geometrico() -> None:
# Intentar crear una instancia de la clase abstracta causará un error
try:
figura = FiguraGeometrica("rojo") # Esto causará un TypeError
except TypeError as e:
print(f"Error al intentar instanciar clase abstracta: {e}")

# Crear instancias de las clases concretas


rectangulo = Rectangulo("azul", 5, 3)
circulo = Circulo("rojo", 2)

# Crear y usar el sistema


sistema = SistemaGeometrico()
sistema.agregar_figura(rectangulo)
sistema.agregar_figura(circulo)

# Calcular y mostrar resultados


print(f"Área del rectángulo: {rectangulo.calcular_area()}")
print(f"Perímetro del círculo: {circulo.calcular_perimetro()}")
print(f"Área total en el sistema: {sistema.calcular_area_total()}")

# Usar método no abstracto heredado


print(rectangulo.describir())
print(circulo.describir())

# Ejecutar la demostración
demostrar_sistema_geometrico()

Veamos otro ejemplo más avanzado con una interfaz abstracta para un sistema de
notificaciones:

from abc import ABC, abstractmethod


from typing import Dict, Any, Optional
from datetime import datetime

class NotificacionBase(ABC):
def __init__(self) -> None:
self.fecha_creacion: datetime = datetime.now()

@abstractmethod
def enviar(self, destinatario: str, mensaje: str) -> bool:
"""Envía una notificación al destinatario"""
pass

@abstractmethod
def verificar_estado(self) -> str:
"""Verifica el estado del servicio de notificación"""
pass

# Método template (patrón Template Method)


def procesar_notificacion(self, destinatario: str, mensaje: str) ->
Dict[str, Any]:
"""
Implementa el patrón Template Method para procesar notificaciones
Este método define el esqueleto del algoritmo
"""
estado = self.verificar_estado()
if estado != "ACTIVO":
return {
"exito": False,
"error": f"Servicio no disponible. Estado: {estado}"
}

try:
resultado = self.enviar(destinatario, mensaje)
return {
"exito": resultado,
"fecha": self.fecha_creacion,
"destinatario": destinatario
}
except Exception as e:
return {
"exito": False,
"error": str(e)
}

# Implementaciones concretas
class NotificacionEmail(NotificacionBase):
def __init__(self, servidor_smtp: str) -> None:
super().__init__()
self.servidor_smtp = servidor_smtp
self._conexion_activa = True

def enviar(self, destinatario: str, mensaje: str) -> bool:


# Simulación de envío de email
print(f"Enviando email a {destinatario} vía
{self.servidor_smtp}")
return True

def verificar_estado(self) -> str:


return "ACTIVO" if self._conexion_activa else "INACTIVO"
class NotificacionSMS(NotificacionBase):
def __init__(self, proveedor_sms: str) -> None:
super().__init__()
self.proveedor_sms = proveedor_sms
self._saldo = 100 # Saldo para enviar SMS

def enviar(self, destinatario: str, mensaje: str) -> bool:


if self._saldo <= 0:
raise ValueError("Saldo insuficiente para enviar SMS")
# Simulación de envío de SMS
print(f"Enviando SMS a {destinatario} vía {self.proveedor_sms}")
self._saldo -= 1
return True

def verificar_estado(self) -> str:


return "ACTIVO" if self._saldo > 0 else "SIN_SALDO"

# Sistema que utiliza las notificaciones


class SistemaNotificaciones:
def __init__(self) -> None:
self.servicios: Dict[str, NotificacionBase] = {}

def agregar_servicio(self, nombre: str, servicio: NotificacionBase) -


> None:
self.servicios[nombre] = servicio

def notificar(self, tipo: str, destinatario: str, mensaje: str) ->


Dict[str, Any]:
servicio = self.servicios.get(tipo)
if not servicio:
return {"exito": False, "error": f"Servicio {tipo} no
encontrado"}
return servicio.procesar_notificacion(destinatario, mensaje)

# Demostración de uso
def demostrar_sistema_notificaciones() -> None:
# Crear el sistema
sistema = SistemaNotificaciones()

# Agregar servicios
email_service = NotificacionEmail("smtp.empresa.com")
sms_service = NotificacionSMS("twilioprovider")

sistema.agregar_servicio("email", email_service)
sistema.agregar_servicio("sms", sms_service)

# Enviar notificaciones
resultado_email = sistema.notificar(
"email",
"[email protected]",
"Bienvenido al sistema"
)
print(f"Resultado email: {resultado_email}")

resultado_sms = sistema.notificar(
"sms",
"+1234567890",
"Código de verificación: 123456"
)
print(f"Resultado SMS: {resultado_sms}")

# Ejecutar la demostración
demostrar_sistema_notificaciones()

Puntos importantes sobre métodos abstractos en Python:

1. Definición:
o Se usan con el decorador @abstractmethod
o Deben estar dentro de una clase que hereda de ABC
o No pueden ser instanciados directamente
2. Propósito:
o Definen una interfaz común
o Fuerzan a las clases hijas a implementar ciertos métodos
o Permiten el polimorfismo
3. Ventajas:
o Mejoran la organización del código
o Facilitan el mantenimiento
o Permiten crear sistemas extensibles

También podría gustarte