Funciones de Orden Superior
Métodos para Combinar Diccionarios
1. Usando el operador | (Python 3.9+)
La forma más concisa para combinar múltiples diccionarios, donde los diccionarios posteriores sobrescriben las claves duplicadas:
datos1 = {"a": 1, "b": 2}
datos2 = {"b": 3, "c": 4}
datos3 = {"d": 5}
combinado = datos1 | datos2 | datos3
print(combinado) # Salida: {'a': 1, 'b': 3, 'c': 4, 'd': 5}
2. Usando la sintaxis de desempaquetado **
Compatible con Python 3.5+, desempaqueta los pares clave-valor en un nuevo diccionario:
combinado = {**datos1, **datos2, **datos3}
print(combinado) # Salida: {'a': 1, 'b': 3, 'c': 4, 'd': 5}
3. Usando el método update()
Modifica el primer diccionario en su lugar, adecuado cuando no necesitas conservar el diccionario original:
combinado = datos1.copy() # Copia para no modificar el original
combinado.update(datos2)
combinado.update(datos3)
print(combinado) # Salida: {'a': 1, 'b': 3, 'c': 4, 'd': 5}
4. Usando collections.ChainMap
Crea una vista en cadena sin realmente combinar los diccionarios, buscando en secuencia (útil para configuración múltiple):
from collections import ChainMap
combinado = ChainMap(datos1, datos2, datos3)
print(combinado["b"]) # Salida: 2 (toma el primer valor encontrado)
print(dict(combinado)) # Conversión a diccionario normal: {'a': 1, 'b': 2, 'c': 4, 'd': 5}
5. Manejo dinámico de diccionarios
Usando bucles o functools.reduce para combinar una lista de diccionarios:
from functools import reduce
lista_diccionarios = [datos1, datos2, datos3]
combinado = reduce(lambda x, y: {**x, **y}, lista_diccionarios)
print(combinado) # Salida: {'a': 1, 'b': 3, 'c': 4, 'd': 5}
Puntos clave:
- Manejo de conflictos: Todos los métodos (excepto
ChainMap) sobrescriben claves duplicadas con valores de diccionarios posteriores. - Eficiencia de memoria:
ChainMapes el más eficiente ya que no copia datos, solo crea una vista. - Compatibilidad: El operador
|fue introducido en Python 3.9; versiones anteriores requieren**oupdate().
Módulo io.StringIO
Proporciona una forma de simular operaciones de archivo en memoria, permitiendo manipular cadenas como si fueran archivos.
1. Pruebas unitarias
Al escribir pruebas unitarias, a veces se necesita simular operaciones de entrada/salida de archivos. StringIO permite evitar la creación de archivos reales, haciendo las pruebas más eficientes e independientes.
import io
from unittest.mock import patch
def leer_contenido(archivo):
return archivo.read()
def prueba_leer_contenido():
contenido_prueba = "Este es un texto de prueba."
archivo_mock = io.StringIO(contenido_prueba)
resultado = leer_contenido(archivo_mock)
assert resultado == contenido_prueba
prueba_leer_contenido()
2. Procesamiento de datos
Cuando se necesita procesar y transformar datos de texto, StringIO puede actuar como un búfer temporal, evitando lecturas y escrituras de archivos frceuentes.
import io
# Supongamos que tenemos una cadena con múltiples líneas de datos
datos = "1,2,3\n4,5,6\n7,8,9"
# Crear objeto StringIO
buffer = io.StringIO(datos)
# Procesar cada línea de datos
lineas_procesadas = []
for linea in buffer:
numeros = [int(num) for num in linea.strip().split(',')]
cuadrados = [num ** 2 for num in numeros]
lineas_procesadas.append(','.join(map(str, cuadrados)))
# Combinar los datos procesados en una sola cadena
datos_procesados = '\n'.join(lineas_procesadas)
print(datos_procesados)
3. Registro de logs
En algunos casos, puedes querer almacenar información de logs en memoria en lugar de escribir directamente en un archivo. StringIO puede usarse para este propósito.
import io
import logging
# Crear objeto StringIO como destino de salida para logs
buffer_log = io.StringIO()
# Configurar el registrador
logging.basicConfig(stream=buffer_log, level=logging.INFO)
registrador = logging.getLogger()
# Registrar algunos mensajes
registrador.info("Este es un mensaje de información.")
registrador.warning("Este es un mensaje de advertencia.")
# Obtener el contenido del log
contenido_log = buffer_log.getvalue()
print(contenido_log)
Descriptores
Un descriptor en Python es un concepto útil que permite personalizar el comportamiento del acceso a atributos. Básicamente, un descriptor es una clase que implementa uno o más de los métodos __get__, __set__ y __delete__, que corresponden a la obtención, establecimiento y eliminación de atributos, respectivamente.
Métodos del protocolo de descriptor:
__get__(self, instance, owner)
- Se utiliza para obtener el valor del atributo
instance: La instancia que llama al descriptor (si es una llamada de instancia)owner: La clase propietaria
__set__(self, instance, value)
- Se utiliza para establecer el valor del atributo
instance: La instancia que llama al descriptorvalue: El valor a establecer
__delete__(self, instance)
- Se utiliza para eliminar el atributo
instance: La instancia que llama al descriptor
Tipos de descriptores:
- Descriptor de datos: Implementa tanto
__get__como__set__. Tiene mayor prioridad que los atributos de instancia. - Descriptor no de datos: Solo implementa
__get__(como funciones y métodos). Tiene menor prioridad que los atributos de instancia.
class NumeroPositivo:
def __set__(self, instance, valor):
if valor <= 0:
raise ValueError("El valor debe ser positivo")
instance.__dict__[self.nombre] = valor # Almacenar en el diccionario de la instancia
def __get__(self, instance, owner):
if instance is None:
return self
return instance.__dict__.get(self.nombre) # Leer del diccionario de la instancia
def __set_name__(self, owner, nombre):
self.nombre = nombre # Almacenar el nombre del atributo (compatible con Python 3.6+)
class Rectangulo:
ancho = NumeroPositivo()
alto = NumeroPositivo()
r1 = Rectangulo()
r2 = Rectangulo()
r1.ancho = 5 # Almacenado en el diccionario de r1
r2.ancho = 10 # Almacenado en el diccionario de r2
print(r1.ancho) # 5 (leído del diccionario de r1)
print(r2.ancho) # 10 (leído del diccionario de r2)
Cadenas Unicode
Definición básica
- Unicode: Es un conjunto de caracteres que asigna un número único (llamado punto de código) a casi todos los caracteres del mundo (incluyendo letras, símbolos, emojis, etc.).
- Ejemplo:
Atiene el punto de código UnicodeU+0041中tiene el punto de código UnicodeU+4E2D- 😊 tiene el punto de código Unicode
U+1F60A - UTF-8: Es un esquema de codificación que convierte los puntos de código Unicode en secuencias de bytes que la computadora puede almacenar y transmitir.
- Es una implementación de Unicode (no la única; existen también UTF-16, UTF-32, etc.)
En programación:
- En Python 3, las cadenas (
str) son Unicode por defecto. - Se usan
encode()/decode()para convertir entre cadenas Unicode y bytes UTF-8.
Diferencias entre Python 2 y 3:
- En Python 2, las cadenas comunes se almacenaban como ASCII de 8 bits, mientras que las cadenas Unicode se almacenaban como cadenas Unicode de 16 bits.
- En Python 3, todas las cadenas son Unicode por defecto.
Gestores de contexto en Python
1. Definición básica
Un gestor de contexto es un objeto que implementa los métodos __enter__() y __exit__(), definiendo las acciones que deben ocurrir antes y después de la ejecución de un bloque de código.
2. Funcionamiento
Flujo de ejecución:
with GestorDeContexto() as x:
# Bloque de código
...
Orden real de ejecución:
- Se llama a
GestorDeContexto().__enter__() - El valor devuelto por
__enter__()se asigna ax(si hay una cláusulaas) - Se ejecuta el bloque de código del
with - Se llama a
__exit__()independientemente de si el bloque terminó correctamente o con una excepción
Manejo de excepciones personalizadas:
class ManejadorDeErrores:
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if exc_type is ValueError:
print("Manejando ValueError")
return True # Suprime la excepción
# Otras excepciones se propagan normalmente
with ManejadorDeErrores():
raise ValueError("Error de prueba") # Esta excepción será capturada
El módulo estándar de Python incluye muchos gestores de contexto útiles:
open(): Para operaciones con archivosthreading.Lock(): Para bloqueos de hilosdecimal.localcontext(): Para contexto de operaciones Decimaltempfile.TemporaryFile(): Para archivos temporalesunittest.mock.patch(): Para mocking en pruebas
Métodos __enter__ y __exit__ en Python
Estos dos métodos especiales conforman el protocolo de gestor de contexto de Python, que implementa la funcionalidad de la sentencia with. Su propósito principal es la gestión de recursos y el manejo de excepciones.
Funciones principales:
1. Método __enter__
- Se llama al entrar en el contexto: Se ejecuta al inicio de la sentencia
with. - Valor de retorno: Puede devolver un objeto que será recibido por la palabra clave
as. - Uso típico: Asignación de recursos (como abrir archivos, obtener bloqueos, conectar bases de datos).
2. Método __exit__
- Se llama al salir del contexto: Se ejecuta después de que el bloque de código del
withtermine. - Manejo de excepciones: Puede capturar y manejar excepciones que ocurran en el bloque de código.
- Uso típico: Liberación de recursos (como cerrar archivos, liberar bloqueos, desconectar bases de datos).
Parámetros de __exit__
Cuando ocurre una excepción en el bloque de código del with, __exit__ recibe información sobre la excepción:
exc_type: Tipo de excepción (comoValueError)exc_val: Instancia del objeto de excepciónexc_tb: Objeto traceback
class ManejadorDeArchivos:
def __init__(self, nombre_archivo, modo):
self.nombre_archivo = nombre_archivo
self.modo = modo
def __enter__(self):
self.archivo = open(self.nombre_archivo, self.modo)
return self.archivo # Este valor será recibido por as
def __exit__(self, exc_type, exc_val, exc_tb):
self.archivo.close()
# Si devuelve True, no se lanzará la excepción del bloque with
# Si devuelve None o False, la excepción se lanzará normalmente
# Ejemplo de uso
with ManejadorDeArchivos('prueba.txt', 'w') as f:
f.write('¡Hola, gestor de contexto!')
# El archivo se cerrará automáticamente, incluso si ocurre una excepción durante la escritura
Resumen de características clave:
- Seguridad de recursos: Asegura que los recursos siempre se liberen correctamente.
- Manejo de excepciones: Permite manejar excepciones de manera elegante en los bloques de código.
- Sintaxis concisa: Evita la necesidad de múltiples sentencias
try-finally. - Composibilidad: Varios gestores de contexto pueden anidarse.
with open('a.txt') as f1, open('b.txt') as f2:
# Procesar ambos archivos simultáneamente
datos1 = f1.read()
datos2 = f2.read()
# Ambos archivos se cerrarán automáticamente
Uso de yield
yield es una palabra clave en Python utilizada para crear generadores. Permite que una función se convierta en una función generadora, implementando cómputo perezoso y mantenimiento de estado.
1. Creación de un generador simple
def generador_simple():
yield 1
yield 2
yield 3
gen = generador_simple()
print(next(gen)) # Salida: 1
print(next(gen)) # Salida: 2
print(next(gen)) # Salida: 3
# print(next(gen)) # Lanza excepción StopIteration
2. Expresiones generadoras
numeros = (x for x in range(10) if x % 2 == 0)
print(list(numeros)) # Salida: [0, 2, 4, 6, 8]
3. Uso del método send() para pasar valores
def acumulador():
total = 0
while True:
valor = yield total
if valor is None:
break
total += valor
acc = acumulador()
next(acc) # Iniciar el generador
print(acc.send(10)) # Salida: 10
print(acc.send(20)) # Salida: 30
print(acc.send(5)) # Salida: 35
acc.close() # Cerrar el generador
4. Sintaxis yield from (Python 3.3+)
Delega la producción de valores a otro generador:
def generador_secundario():
yield 1
yield 2
def generador_principal():
yield 'inicio'
yield from generador_secundario()
yield 'fin'
for item in generador_principal():
print(item)
# Salida: inicio 1 2 fin
5. Implementación de corrutinas
yield puede usarse para implementar corrutinas simples:
def corrutina():
while True:
recibido = yield
print(f"Recibido: {recibido}")
co = corrutina()
next(co) # Iniciar la corrutina
co.send("Mensaje 1") # Salida: Recibido: Mensaje 1
co.send("Mensaje 2") # Salida: Recibido: Mensaje 2
Función sorted
sorted() es una función de ordenamiento de orden superior incorporada en Python, que ordena objetos iterables y devuelve una nueva lista ordenada.
sorted(iterable, *, key=None, reverse=False)
| Parámetro | Descripción |
|---|---|
iterable |
Objeto iterable a ordenar (listas, tuplas, cadenas, diccionarios, conjuntos, etc.) |
key |
Función para extraer una clave de comparación de cada elemento (opcional) |
reverse |
Booleano, True para orden descendiente, False para ascendiente (predeterminado False) |
Ejemplos:
# Ordenación de tuplas
tuplas = [(1, 'b'), (2, 'a'), (0, 'a')]
tuplas_ordenadas = sorted(tuplas, key=lambda x: x[0])
print(tuplas_ordenadas)
# Ordenación por longitud de cadena
palabras = ["banana", "tarta", "manzana", "naranja"]
palabras_ordenadas = sorted(palabras, key=lambda x: len(x))
print(palabras_ordenadas) # Salida: ['tarta', 'manzana', 'banana', 'naranja']
# Ordenación multi-criterio
estudiantes = [
{"nombre": "Ana", "puntuacion": 85},
{"nombre": "Luis", "puntuacion": 90},
{"nombre": "Carlos", "puntuacion": 85}
]
estudiantes_ordenados = sorted(estudiantes, key=lambda x: (-x["puntuacion"], x["nombre"]))
print(estudiantes_ordenados)
# Ordenación de diccionario por clave o valor
notas = {'Ana': 85, 'Luis': 90, 'Carlos': 78}
# Ordenado por clave
ordenado_por_nombre = sorted(notas.items())
print(ordenado_por_nombre) # [('Ana', 85), ('Carlos', 78), ('Luis', 90)]
# Ordenado por valor
ordenado_por_nota = sorted(notas.items(), key=lambda x: x[1])
print(ordenado_por_nota) # [('Carlos', 78), ('Ana', 85), ('Luis', 90)]
Módulo collections
1. defaultdict
Función: Diccionario con valores por defecto, que no lanza KeyError al acceder a claves inexistentes.
Casos de uso: Conteo, agrupación, construcción de diccionarios de diccionarios, etc.
from collections import defaultdict
# Ejemplo 1: Conteo de palabras apariciones
conteo_palabras = defaultdict(int) # Valor por defecto: 0
for palabra in ['manzana', 'banana', 'manzana', 'naranja']:
conteo_palabras[palabra] += 1
# defaultdict(<class 'int'>, {'manzana': 2, 'banana': 1, 'naranja': 1})
# Ejemplo 2: Agrupación por inicial
palabras_por_inicial = defaultdict(list) # Valor por defecto: lista vacía
for palabra in ['manzana', 'banana', 'naranja', 'aguacate']:
palabras_por_inicial[palabra[0]].append(palabra)
# defaultdict(<class 'list'>, {'m': ['manzana'], 'b': ['banana'], 'n': ['naranja'], 'a': ['aguacate']})
# Agrupación por categoría
datos = [('manzana', 'fruta'), ('banana', 'fruta'), ('zanahoria', 'verdura')]
categorias = defaultdict(list)
for nombre, categoria in datos:
categorias[categoria].append(nombre)
print(dict(categorias))
Comparación con dict.setdefault():
d = {'a': 1, 'b': 2}
# Clave inexistente
print(d.setdefault('c', 3)) # Salida: 3
print(d) # Salida: {'a': 1, 'b': 2, 'c': 3}
2. Counter
Función: Contador para contar apariciones de objetos hashables.
Casos de uso: Estadística de frecuencia, problemas TopN, etc.
from collections import Counter
# Ejemplo 1: Conteo de elementos
cnt = Counter(['rojo', 'azul', 'rojo', 'verde', 'azul', 'azul'])
print(cnt) # Counter({'azul': 3, 'rojo': 2, 'verde': 1})
# Ejemplo 2: Obtener los 2 elementos más comunes
print(cnt.most_common(2)) # [('azul', 3), ('rojo', 2)]
# Ejemplo 3: Conteo de frecuencia de palabras en texto
palabras = "cuántas veces aparece cada palabra en esta oración".split()
conteo_palabras = Counter(palabras)
print(conteo_palabras.most_common(3))
3. deque
Función: Cola de doble extremo, segura para hilos, permite operaciones eficientes en ambos extremos.
Casos de uso: Colas, pilas, ventanas deslizantes, búsqueda en anchura, etc.
from collections import deque
# Ejemplo 1: Uso como cola
d = deque()
d.append('a') # Agregar al extremo derecho
d.appendleft('b') # Agregar al extremo izquierdo
print(d) # deque(['b', 'a'])
d.pop() # Eliminar del extremo derecho
d.popleft() # Eliminar del extremo izquierdo
# Ejemplo 2: Cola de tamaño fijo
ultimas_tres = deque(maxlen=3)
for i in range(5):
ultimas_tres.append(i)
print(ultimas_tres)
# deque([0], maxlen=3)
# deque([0, 1], maxlen=3)
# deque([0, 1, 2], maxlen=3)
# deque([1, 2, 3], maxlen=3)
4. namedtuple
Función: Tupla con nombre, crea una subclase de tupla con campos con nombre.
Casos de uso: Reemplazo de clases simples para código más legible.
from collections import namedtuple
# Ejemplo 1: Definir estructura Punto
Punto = namedtuple('Punto', ['x', 'y'])
p = Punto(11, y=22) # Se puede crear con posición o argumentos por palabra clave
print(p.x, p.y) # 11 22
print(p[0], p[1]) # 11 22 (también soporta acceso por índice)
# Ejemplo 2: Reemplazo de clase simple
Persona = namedtuple('Persona', ['nombre', 'edad', 'genero']
juan = Persona(nombre='Juan', edad=30, genero='masculino')
print(f"{juan.nombre} tiene {juan.edad} años")
5. OrderedDict (en Python 3.7+ dict ya está ordenado)
Función: Diccionario ordenado, recuerda el orden de inserción de claves.
Casos de uso: Operaciones con diccionario que requieren mantener el orden de inserción.
from collections import OrderedDict
# Ejemplo 1: Mantener orden de inserción
d = OrderedDict()
d['a'] = 1
d['b'] = 2
d['c'] = 3
print(list(d.keys())) # ['a', 'b', 'c']
6. ChainMap
Función: Enlaza múltiples diccionarios o mapeos en una sola vista.
Casos de uso: Configuración multinivel, simulación de ámbitos de variables, etc.
from collections import ChainMap
# Ejemplo 1: Combinar múltiples diccionarios
dict1 = {'a': 1, 'b': 2}
dict2 = {'b': 3, 'c': 4}
combinado = ChainMap(dict1, dict2)
print(combinado['a']) # 1 (de dict1)
print(combinado['b']) # 2 (de dict1, tiene prioridad)
print(combinado['c']) # 4 (de dict2)
JSON
JSON (Notación de Objetos JavaScript) es un formato ligero de intercambio de datos de texto.
import json
# Diccionario Python convertido a objeto JSON
datos1 = {
'no': 1,
'nombre': 'Ejemplo',
'url': 'http://www.ejemplo.com'
}
json_str = json.dumps(datos1)
print ("Datos Python originales:", repr(datos1))
print ("Objeto JSON:", json_str)
# Objeto JSON convertido a diccionario Python
datos2 = json.loads(json_str)
print ("datos2['nombre']: ", datos2['nombre'])
print ("datos2['url']: ", datos2['url'])
Para manejar archivos en lugar de cadenas, se pueden usar json.dump() y json.load() para codificar y decodificar datos JSON:
import json
# Preparar estructura de datos Python para almacenar
datos = {
"nombre": "María",
"edad": 25,
"es_estudiante": False,
"cursos": ["Matemáticas", "Física", "Programación"],
"direccion": {
"calle": "Calle Principal 123",
"ciudad": "Madrid"
}
}
# Escribir datos en archivo JSON
with open('usuario_datos.json', 'w', encoding='utf-8') as f:
json.dump(datos, f, ensure_ascii=False, indent=4)
print("Datos escritos en usuario_datos.json")
# Leer datos desde archivo JSON
with open('usuario_datos.json', 'r', encoding='utf-8') as f:
datos_cargados = json.load(f)
print("Datos cargados desde archivo:")
print(datos_cargados)
Diferencias entre JSON y diccionario Python:
| Característica | JSON | Diccionario Python |
|---|---|---|
| Naturaleza | Formato de intercambio de datos (cadena) | Estructura de datos incorporada (objeto en memoria) |
| Tipo de clave | Solo cadenas (con comillas dobles ") |
Cualquier tipo hashable (cadenas, números, tuplas, etc.) |
| Tipo de valor | Soporta cadenas, números, booleanos, null, listas, objetos JSON |
Soporta todos los tipos de datos de Python (como None, tuple, set, funciones, etc.) |
| Detalles de sintaxis | Cadenas deben usar comillas dobles "; no puede haber coma final |
Cadenas pueden usar comillas simples ' o dobles "; puede haber coma final |
| Booleanos | true, false (minúsculas) |
True, False (mayúsculas) |
| Nulo | null |
None |
| Comentarios | No soporta comentarios | Soporta comentarios (como parte del código Python) |
| Uso | Transferencia de datos (APIs), almacenamiento de datos | Procesamiento interno de datos en Python |
Expresiones Regulares
Patrones de expresiones regulares pueden incluir:
- Caracteres literales: letras, números, espacios, etc., que coinciden consigo mismos.
- Caracteres especiales: como
.,*,+,?, etc., que tienen significado especial.
| Carácter | Descripción | Ejemplo | Nota mnemotécnica |
|---|---|---|---|
. |
Coincide con cualquier carácter (excepto salto de línea) | a.c → 'abc', 'a c' |
Cualquier punto |
[ ] |
Coincide con cualquier carácter dentro de los corchetes | [abc] coincide con 'a', 'b' o 'c' |
Como una lista de compra, elige uno |
| ` | ` | O lógico | a|b → 'a' o 'b' |
( ... ) |
Agrupación y captura | (ab)+ → 'abab' |
Paréntesis → "agrupar como un conjunto" |
\d |
Dígito ([0-9]) |
\d+ → '123' |
Dígito |
\D |
No dígito ([^0-9]) |
\D+ → 'abc' |
|
\s |
Carácter de espacio (espacio, tabulación, etc.) | a\sb → 'a b' |
Espacio |
\S |
No carácter de espacio | \S+ → 'abc' |
|
\w |
Carácter de palabra ([a-zA-Z0-9_]) |
\w+ → 'abc_123' |
Palabra |
\W |
No carácter de palabra | \W+ → '@#$' |
|
* |
0 o más veces | a* → '', 'a', 'aa' |
Como un cero |
+ |
1 o más veces | a+ → 'a', 'aa' |
Como un signo más, al menos uno |
? |
0 o 1 vez | a? → '', 'a' |
¿Cero o uno? |
{n} |
Exactamente n veces | a{2} → 'aa' |
|
{n,} |
Al menos n veces | a{2,} → 'aa', 'aaa' |
|
{n,m} |
Entre n y m veces | a{2,3} → 'aa', 'aaa' |
|
^ |
Coincide con el inicio de la cadena | ^abc → comienza con 'abc' |
Flecha hacia arriba, apunta al inicio |
$ |
Coincide con el final de la cadena | abc$ → termina con 'abc' |
Signo de dólar al final |
\b |
Límite de palabra | \bword → palabras que comienzan con "word" |
b de boundary (límite) |
\B |
No límite de palabra |
Ejemplo: Validación de correo electrónico
- Patrón básico:
\w+@\w+\.\w+ \w+es carácter de palabra,@y.son literales →usuario@ejemplo.com- Donde
\\.escapa el punto para que sea tratado literalmente
Para una validación más realista se necesita una expresión regular más estricta:
r'^[\w.-]+@[\w.-]+\.[a-zA-Z]{2,}$'
En expresiones regulares, los siguientes caracteres deben escaparse con \ para coincidir literalmente:
. * ? + ^ $ [ ] { } ( ) | \
Uso de raise
1. Uso básico
1.1 Lanzar excepciones incorporadas
# Lanzar un ValueError simple
raise ValueError("Este es un mensaje de error")
# Equivalente a
raise ValueError("Este es un mensaje de error")
Salida:
Traceback (most recent call last):
File "ejemplo.py", line 1, in <module>
raise ValueError("error")
ValueError: error
1.2 Relanzar la excepción actual
En un bloque de manejo de excepciones, se puede usar raise sin argumentos para relanzar la excepción actual que se está manejando:
try:
# Código que podría generar un error
x = 1 / 0
except ZeroDivisionError:
print("Capturado error de división por cero")
raise # Relanza la misma excepción
Relación con try-except: raise y try-except suelen usarse juntos:
def procesar_datos(datos):
if not datos:
raise ValueError("Los datos no pueden estar vacíos")
# Procesar datos...
try:
procesar_datos(None)
except ValueError as e:
print(f"Capturado error: {e}")
Módulo pickle (serialización y deserialización)
pickle es un módulo estándar de Python para la serialización y deserialización de objetos (persistencia de objetos de Python). Permite convertir objetos de Python en un flujo de bytes (serialización) y reconstruir objetos a partir de ese flujo (deserialización).
# Serialización
import pickle
datos = {
'nombre': 'Ana',
'edad': 30,
'aficiones': ['lectura', 'senderismo']
}
# Serializar a archivo
with open('datos.pkl', 'wb') as f:
pickle.dump(datos, f)
# Serializar a bytes
datos_bytes = pickle.dumps(datos)
print(datos_bytes) # Salida de bytes
# Deserialización
# Deserializar desde archivo
with open('datos.pkl', 'rb') as f:
datos_cargados = pickle.load(f)
print(datos_cargados)
# Deserializar desde bytes
datos_originales = pickle.loads(datos_bytes)
print(datos_originales)
Destrucción de objetos (recolección de basura) en Python
En Python, la recolección de objetos se maneja a través del mecanismo de recolección de basura (Garbage Collection, GC). Python utiliza el conteo de referencias como mecanismo principal de gestión de memoria, combinado con un recolector de basura circular para manejar referencias circulares. A continuación se detalla la recolección de objetos en Python.
1. Conteo de referencias (Reference Counting)
Cada objeto en Python tiene un conteo de referencias que registra cuántas referencias apuntan a ese objeto. Cuando el conteo de referencias llega a 0, el objeto se recupera inmediatamente.
Reglas del conteo de referencias:
- Cuando se crea un objeto, su conteo de referencias es 1.
- Cuando un objeto se asigna a una variable, se agrega a un contenedor (como listas, diccionarios) o se pasa como parámetro, el conteo de referencias aumenta.
- Cuando una variable se reasigna, sale del ámbito o se elimina un contenedor, el conteo de referencias disminuye.
- Cuando el conteo de referencias llega a 0, el objeto se destruye inmediatamente y se libera la memoria.
import sys
# Crear un objeto
a = [1, 2, 3]
print(sys.getrefcount(a)) # Salida de conteo de referencias, aquí es 2 (a + parámetro getrefcount)
# Aumentar referencia
b = a
print(sys.getrefcount(a)) # Salida de conteo de referencias, aquí es 3 (a, b + parámetro getrefcount)
# Disminuir referencia
del b
print(sys.getrefcount(a)) # Salida de conteo de referencias, aquí es 2 (a + parámetro getrefcount)
# Disminuir referencia
del a
# Ahora el conteo de referencias es 0, el objeto se recupera
2. Recolector de basura circular (Cycle Detector)
El conteo de referencias no puede manejar referencias circulares. Por ejemplo, si dos objetos se refieren mutuamente pero no hay referencias externas que apunten a ellos, su conteo de referencias nunca llegará a 0, causando una fuga de memoria. Para resolver esto, Python introduce el recolector de basura circular.
Principio de funcionamiento del recolector de basura circular:
- El recolector de basura de Python verifica periódicamente las relaciones de referencias entre objetos.
- Si detecta un grupo de objetos que se refieren mutuamente pero no hay referencias externas que apunten a ellos, marca este grupo de objetos como basura y los recupera.
class Nodo:
def __init__(self, valor):
self.valor = valor
self.siguiente = None
# Crear referencia circular
nodo1 = Nodo(1)
nodo2 = Nodo(2)
nodo1.siguiente = nodo2
nodo2.siguiente = nodo1
# Eliminar referencias externas
del nodo1
del nodo2
# Ahora nodo1 y nodo2 se refieren mutuamente, pero su conteo de referencias no es 0
# El recolector de basura circular detectará esta situación y los recuperará
3. Disparo de la recolección de basura
El mecanismo de recolección de basura de Python se dispara en los siguientes casos:
- Conteo de referencias llega a 0: El objeto se recupera inmediatamente.
- Llamada explícita a
gc.collect(): Disparo manual de la recolección de basura. - Alcanzar umbral: El recolector de basura de Python ajusta dinámicamente los umbrales según la asignación y liberación de objetos. Cuando se alcanza el umbral, se dispara automáticamente la recolección de basura.
Ejemplo: Disparo manual de recolección de basura
import gc
# Crear algunos objetos
a = [1, 2, 3]
b = [4, 5, 6]
a.append(b)
b.append(a)
# Eliminar referencias externas
del a
del b
# Disparar manualmente la recolección de basura
gc.collect() # Devuelve el número de objetos recuperados
4. Método __del__
Python proporciona un método __del__ que puede realizar operaciones de limpieza cuando un objeto se recupera. Sin embargo, se debe tener en cuenta:
- El momento de llamada a
__del__depende del recolector de basura, no se garantiza que se ejecute inmediatamente. - Si hay referencias circulares entre objetos,
__del__podría no ser llamado.
class MiClase:
def __init__(self, nombre):
self.nombre = nombre
def __del__(self):
print(f"El objeto {self.nombre} está siendo destruido")
# Crear objeto
obj = MiClase("ejemplo")
# Eliminar objeto
del obj # Salida: El objeto ejemplo está siendo destruido
Método seek en archivos
En Python, el error io.UnsupportedOperation: can't do nonzero cur-relative seeks ocurre generalmente cuando se intenta usar el método seek() en un archivo abierto en modo texto ('r' o 'w'), pasando un offset distinto de cero y from=1 (relativo a la posición actual) o from=2 (relativo al final del archivo).
Causa del error
En modo texto, el objeto de archivo de Python no soporta desplazamientos no cero relativos a la posición actual o al final del archivo (cur-relative seeks o end-relative seeks). Esto se debe a que el contenido del archivo en modo texto puede estar codificado (como UTF-8), lo que hace que los desplazamientos de bytes no coincidan con los desplazamientos de caracteres, imposibilitando el cálculo preciso de posiciones.
Métodos de solución
Método 1: Usar modo binario
Si necesitas usar seek() con desplazamientos relativos a la posición actual o al final del archivo, puedes abrir el archivo en modo binario ('rb' o 'wb'). En modo binario, el comportamiento de seek() es preciso y soporta cualquier desplazamiento y posición de referencia.
with open('ejemplo.txt', 'rb') as f: # Abrir archivo en modo binario
f.seek(5, 1) # Mover 5 bytes desde la posición actual
datos = f.read(10) # Leer 10 bytes
print(datos)
Pero en este caso se devuelve texto en formato binario, que necesita ser decodificado:
datos = datos.decode(encoding="utf-8", errors="strict")
Método 2: Usar solo seek() desde el inicio del archivo
Si debes operar en modo texto, puedes usar solo seek() desde el inicio del archivo (from=0), que es el único soportado en modo texto.
with open('ejemplo.txt', 'r') as f: # Abrir archivo en modo texto
f.seek(10) # Mover 10 bytes desde el inicio del archivo
datos = f.read(5) # Leer 5 caracteres
print(datos)
Método 3: Calcular posición manualmente
Si necesitas mover el puntero desde el final del archivo o desde la posición actual, puedes usar tell() para obtener la posición actual, calcular manualmente el desplazamiento y luego usar seek(offset, 0).
with open('ejemplo.txt', 'r') as f:
pos_actual = f.tell() # Obtener posición actual
f.seek(pos_actual + 5, 0) # Calcular manualmente el desplazamiento, mover desde el inicio
datos = f.read(5)
print(datos)
Comprensiones de lista
Ejemplo:
# Generar lista de cuadrados
cuadrados = [x**2 for x in range(10])
# Comprensión de lista anidada
matriz = [[i + j for j in range(3)] for i in range(3)]
Equivalente a:
matriz = [] # Inicializar lista vacía
for i in range(3): # Bucle externo, i de 0 a 2
fila = [] # Inicializar fila vacía
for j in range(3): # Bucle interno, j de 0 a 2
fila.append(i + j) # Agregar resultado i+j a la fila actual
matriz.append(fila) # Agregar fila actual a la matriz
# Generación de lista con condiciones
numeros_positivos_pequeños = [i for i in numeros if i > 0 and i < 5]
Comparación entre comprensión de lista y map/filter:
# Usando map y filter
numeros = [1, 2, 3, 4, 5]
cuadrados_pares = list(map(lambda x: x ** 2, filter(lambda x: x % 2 == 0, numeros)))
# Usando comprensión de lista
numeros = [1, 2, 3, 4, 5]
cuadrados_pares = [x ** 2 for x in numeros if x % 2 == 0]
Paso de parámetros en funciones de Python
- Tipos inmutables: Similar a paso por valor en C++, como enteros, cadenas, tuplas. Al llamar a una función con un parámetro como
fun(a), se pasa solo el valor dea, sin afectar el objetoaen sí. Por ejemplo, modificar el valor deadentro defun(a)solo modifica otro objeto copiado, no afecta alaoriginal. - Tipos mutables: Similar a paso por referencia en C++, como listas, diccionarios. Al llamar a
fun(lista), se pasa realmente el objetolista, y las modificaciones dentro de la función afectarán allistaexterno.
Módulo calendar
(1) Verificación de año bisiesto
Usar la función isleap() para determinar si un año es bisiesto.
import calendar
print(calendar.isleap(2024)) # True
print(calendar.isleap(2023)) # False