Python
Tutorial Python: Entrada y salida avanzada
Aprende técnicas avanzadas de entrada y salida en Python, incluyendo redirección, flujos estándar y manejo de datos binarios para programas robustos.
Aprende Python y certifícateRedirección de entrada/salida
La redirección de entrada/salida es una técnica fundamental en Python que permite cambiar el origen de los datos de entrada o el destino de los datos de salida durante la ejecución de un programa. Esta capacidad resulta especialmente útil para automatizar procesos, realizar pruebas o crear flujos de datos más complejos.
En Python, podemos redirigir la entrada y salida estándar de varias maneras, desde técnicas básicas hasta métodos más avanzados. Vamos a explorar las principales formas de hacerlo.
Redirección temporal con contextos
La forma más elegante y segura de redirigir la entrada/salida en Python moderno es mediante el uso de gestores de contexto. Estos garantizan que los flujos de E/S vuelvan a su estado original incluso si ocurren excepciones.
Para redirigir la salida estándar a un archivo:
import sys
# Redirección de la salida estándar a un archivo
with open('salida.txt', 'w') as archivo_salida:
# Guardamos la referencia original
stdout_original = sys.stdout
# Redirigimos stdout al archivo
sys.stdout = archivo_salida
# Todo lo que se imprima ahora irá al archivo
print("Este texto se guardará en el archivo")
print("También esta línea")
# Restauramos stdout a su valor original
sys.stdout = stdout_original
# Aquí ya estamos fuera del contexto y print vuelve a la consola
print("Este texto aparecerá en la consola")
De manera similar, podemos redirigir la entrada estándar:
import sys
# Simulamos entrada desde un archivo
with open('entrada.txt', 'r') as archivo_entrada:
# Guardamos la referencia original
stdin_original = sys.stdin
# Redirigimos stdin al archivo
sys.stdin = archivo_entrada
# input() leerá del archivo en lugar de esperar entrada del usuario
linea = input()
print(f"Leído del archivo: {linea}")
# Restauramos stdin a su valor original
sys.stdin = stdin_original
Implementación con clases personalizadas
Para casos más complejos, podemos crear nuestras propias clases que imiten los objetos de archivo estándar:
import sys
class CapturaSalida:
def __init__(self):
self.contenido = []
def write(self, texto):
self.contenido.append(texto)
def flush(self):
pass # Necesario para compatibilidad con sys.stdout
def obtener_contenido(self):
return ''.join(self.contenido)
# Uso de la clase personalizada
captura = CapturaSalida()
stdout_original = sys.stdout
sys.stdout = captura
print("Esto será capturado")
print("Y esto también")
sys.stdout = stdout_original # Restauramos stdout
# Ahora podemos acceder a lo que se imprimió
texto_capturado = captura.obtener_contenido()
print(f"Contenido capturado: {texto_capturado}")
Esta técnica es especialmente útil para pruebas unitarias donde necesitamos verificar que una función produce la salida esperada.
Redirección con contextlib
Python ofrece una forma aún más elegante de redirigir la salida utilizando el módulo contextlib
:
import contextlib
import io
import sys
# Capturamos la salida estándar
with contextlib.redirect_stdout(io.StringIO()) as salida:
print("Este texto será capturado")
print("Este también")
# Obtenemos el contenido capturado
contenido = salida.getvalue()
print(f"Contenido capturado: {contenido}")
Para la entrada, podemos combinar StringIO
con la redirección manual:
import io
import sys
# Creamos una fuente de entrada simulada
entrada_simulada = io.StringIO("línea1\nlínea2\nlínea3")
stdin_original = sys.stdin
sys.stdin = entrada_simulada
# Ahora input() leerá de nuestro StringIO
for _ in range(3):
linea = input()
print(f"Leído: {linea}")
# Restauramos la entrada estándar
sys.stdin = stdin_original
Redirección para subprocesos
Cuando trabajamos con subprocesos, podemos redirigir su entrada/salida de manera eficiente:
import subprocess
# Redirigimos la salida de un comando externo a una variable
resultado = subprocess.run(
["python", "-c", "print('Hola desde subproceso')"],
capture_output=True,
text=True
)
print(f"Salida del subproceso: {resultado.stdout}")
# Proporcionamos entrada a un subproceso
entrada = "datos de entrada"
resultado = subprocess.run(
["python", "-c", "import sys; print(sys.stdin.read())"],
input=entrada,
capture_output=True,
text=True
)
print(f"El subproceso recibió: {resultado.stdout}")
Redirección permanente a nivel de script
Si necesitamos redirigir la salida durante toda la ejecución de un script, podemos hacerlo al inicio:
import sys
# Redirigimos toda la salida del script a un archivo
sys.stdout = open('registro_completo.log', 'w')
# A partir de aquí, todo se escribirá en el archivo
print("Inicio del script")
# ... resto del código ...
print("Fin del script")
# Es importante cerrar el archivo al finalizar
sys.stdout.close()
Sin embargo, esta técnica debe usarse con precaución, ya que si ocurre una excepción antes de cerrar el archivo, podría quedar abierto. Es preferible usar los gestores de contexto mencionados anteriormente.
Aplicaciones prácticas
La redirección de E/S tiene numerosas aplicaciones:
- Registro (logging): Guardar la salida de un programa en archivos de registro.
import sys
import datetime
# Redirección con timestamp
with open(f"log_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.txt", 'w') as log:
with contextlib.redirect_stdout(log):
print("Iniciando proceso...")
# Código que genera salida que queremos registrar
for i in range(5):
print(f"Procesando item {i}")
print("Proceso completado")
- Pruebas automatizadas: Verificar que una función produce la salida esperada.
import io
import contextlib
def test_funcion_salida():
# Capturamos la salida de una función
with io.StringIO() as buffer, contextlib.redirect_stdout(buffer):
calcular_estadisticas([1, 2, 3, 4, 5])
salida = buffer.getvalue()
# Verificamos que la salida contiene lo esperado
assert "Media: 3.0" in salida
assert "Mediana: 3" in salida
- Simulación de entrada de usuario: Automatizar interacciones que normalmente requieren entrada manual.
import sys
import io
def test_interfaz_usuario():
# Simulamos respuestas del usuario
entradas = io.StringIO("Juan\n25\nsí\n")
sys.stdin = entradas
# Ejecutamos la función que normalmente pediría input
resultado = recopilar_datos_usuario()
# Restauramos stdin
sys.stdin = sys.__stdin__
# Verificamos el resultado
assert resultado["nombre"] == "Juan"
assert resultado["edad"] == 25
assert resultado["confirmado"] == True
La redirección de entrada/salida es una herramienta poderosa que, cuando se domina, permite crear programas más flexibles y robustos, especialmente en entornos donde la automatización y las pruebas son importantes.
Uso de sys.stdin, sys.stdout y sys.stderr
Python proporciona acceso directo a los flujos estándar del sistema operativo a través del módulo sys
. Estos tres objetos representan los canales fundamentales de comunicación entre un programa y su entorno: entrada estándar, salida estándar y salida de error.
Conceptos básicos de los flujos estándar
Los tres flujos estándar en Python son objetos tipo archivo que están disponibles inmediatamente al importar el módulo sys
:
import sys
# Los tres flujos estándar
entrada = sys.stdin # Entrada estándar
salida = sys.stdout # Salida estándar
error = sys.stderr # Salida de error
Cada uno de estos objetos implementa la interfaz de archivo de Python, lo que significa que podemos usar métodos como read()
, write()
y flush()
directamente con ellos.
Trabajando con sys.stdin
El objeto sys.stdin
representa la entrada estándar, que normalmente está conectada al teclado. Cuando usamos la función input()
en Python, esta función realmente lee de sys.stdin
.
Podemos leer directamente de sys.stdin
de varias formas:
import sys
# Leer una línea
linea = sys.stdin.readline()
print(f"Leíste: {linea}")
# Leer múltiples líneas hasta EOF (Ctrl+D en Unix, Ctrl+Z en Windows)
print("Introduce varias líneas (termina con Ctrl+D o Ctrl+Z):")
contenido = sys.stdin.read()
print(f"Contenido completo:\n{contenido}")
# Iterar sobre las líneas de entrada
print("Introduce líneas (termina con Ctrl+D o Ctrl+Z):")
for linea in sys.stdin:
print(f"Procesando: {linea.strip()}")
Una aplicación práctica es procesar datos de entrada línea por línea sin cargar todo en memoria:
import sys
# Procesamiento eficiente de grandes volúmenes de datos
total = 0
contador = 0
print("Introduce números (uno por línea):")
for linea in sys.stdin:
try:
numero = float(linea.strip())
total += numero
contador += 1
except ValueError:
print(f"Ignorando valor no numérico: {linea.strip()}", file=sys.stderr)
if contador > 0:
print(f"Media de {contador} números: {total/contador:.2f}")
else:
print("No se introdujeron números válidos")
Trabajando con sys.stdout
El objeto sys.stdout
representa la salida estándar, que normalmente está conectada a la consola o terminal. La función print()
escribe en sys.stdout
por defecto.
Podemos escribir directamente en sys.stdout
:
import sys
# Escribir texto directamente
sys.stdout.write("Esto es un texto sin salto de línea")
sys.stdout.write(" que continúa en la misma línea\n")
# Forzar que los datos se envíen inmediatamente
sys.stdout.write("Datos importantes...")
sys.stdout.flush() # Asegura que el texto se muestre inmediatamente
Una diferencia importante entre print()
y sys.stdout.write()
es que print()
añade automáticamente un salto de línea y puede manejar múltiples argumentos, mientras que write()
es más básico:
# Con print()
print("Hola", "mundo", 123, sep=", ") # Salida: Hola, mundo, 123
# Con sys.stdout.write()
sys.stdout.write("Hola, mundo, 123\n") # Debemos formatear manualmente
Trabajando con sys.stderr
El objeto sys.stderr
representa la salida de error estándar, un canal separado para mensajes de error y diagnóstico. Aunque visualmente puede parecer igual que stdout
en la consola, es un flujo independiente que puede redirigirse por separado.
import sys
# Mensaje normal
sys.stdout.write("Esto es información normal\n")
# Mensaje de error
sys.stderr.write("¡ADVERTENCIA! Esto es un mensaje de error\n")
La ventaja de usar sys.stderr
para errores es que podemos separar la salida normal de los mensajes de error:
import sys
def procesar_archivo(nombre):
try:
with open(nombre, 'r') as f:
return len(f.readlines())
except FileNotFoundError:
print(f"ERROR: No se encontró el archivo '{nombre}'", file=sys.stderr)
return 0
# Uso
archivos = ["datos.txt", "noexiste.txt", "config.ini"]
for archivo in archivos:
lineas = procesar_archivo(archivo)
print(f"El archivo {archivo} tiene {lineas} líneas")
Al ejecutar este script desde la terminal, podríamos redirigir solo la salida normal a un archivo mientras vemos los errores en pantalla:
python script.py > resultados.txt
Personalización de los flujos estándar
Podemos modificar temporalmente los flujos estándar para cambiar su comportamiento:
import sys
# Clase para añadir prefijos a la salida
class SalidaConPrefijo:
def __init__(self, salida_original, prefijo):
self.salida = salida_original
self.prefijo = prefijo
def write(self, texto):
# Añadimos el prefijo a cada línea
lineas = texto.split('\n')
for i, linea in enumerate(lineas):
if linea or i < len(lineas) - 1: # Evitamos añadir prefijo a líneas vacías finales
self.salida.write(f"{self.prefijo}{linea}\n")
def flush(self):
self.salida.flush()
# Guardamos las referencias originales
stdout_original = sys.stdout
stderr_original = sys.stderr
# Reemplazamos con nuestras versiones personalizadas
sys.stdout = SalidaConPrefijo(stdout_original, "[INFO] ")
sys.stderr = SalidaConPrefijo(stderr_original, "[ERROR] ")
# Ahora todas las salidas tendrán prefijos
print("Operación completada correctamente")
print("Se procesaron 10 elementos", file=sys.stderr)
# Restauramos los flujos originales
sys.stdout = stdout_original
sys.stderr = stderr_original
Detección de características del terminal
Los objetos de flujo estándar también nos permiten detectar si están conectados a un terminal interactivo o a otro tipo de flujo:
import sys
# Verificar si estamos en un terminal interactivo
if sys.stdout.isatty():
print("La salida está conectada a un terminal interactivo")
# Podemos usar códigos ANSI para colorear la salida
print("\033[31mTexto en rojo\033[0m")
print("\033[1;32mTexto en verde brillante\033[0m")
else:
print("La salida está redirigida a un archivo o tubería")
# Evitamos usar códigos de color que ensuciarían la salida
Codificación de caracteres
Los flujos estándar tienen una codificación asociada que puede ser importante cuando trabajamos con caracteres no ASCII:
import sys
# Mostrar la codificación de los flujos estándar
print(f"Codificación de stdin: {sys.stdin.encoding}")
print(f"Codificación de stdout: {sys.stdout.encoding}")
print(f"Codificación de stderr: {sys.stderr.encoding}")
# Escribir caracteres Unicode
sys.stdout.write("Símbolo de euro: €\n")
sys.stdout.write("Emoji: 🐍\n")
Uso práctico en aplicaciones reales
Los flujos estándar son especialmente útiles en aplicaciones de línea de comandos y scripts de procesamiento:
import sys
import json
def procesar_json():
"""Lee un JSON de stdin y escribe una versión procesada en stdout."""
try:
# Leer JSON de la entrada estándar
datos = json.load(sys.stdin)
# Procesar los datos (ejemplo: extraer solo ciertos campos)
resultado = []
for item in datos:
if "nombre" in item and "edad" in item:
resultado.append({
"nombre": item["nombre"].upper(),
"es_adulto": item["edad"] >= 18
})
# Escribir el resultado en la salida estándar
json.dump(resultado, sys.stdout, indent=2)
sys.stdout.write("\n") # Añadir salto de línea final
except json.JSONDecodeError as e:
print(f"Error al decodificar JSON: {e}", file=sys.stderr)
sys.exit(1)
except Exception as e:
print(f"Error inesperado: {e}", file=sys.stderr)
sys.exit(2)
# Ejecutar si se llama como script principal
if __name__ == "__main__":
procesar_json()
Este script podría usarse en una tubería de comandos:
cat datos.json | python procesar.py > resultados.json
Integración con bibliotecas de registro
Los flujos estándar se pueden integrar con sistemas de registro como el módulo logging
:
import sys
import logging
# Configurar logging para escribir en stderr
logging.basicConfig(
stream=sys.stderr,
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
# Crear un manejador para stdout para mensajes no críticos
handler_info = logging.StreamHandler(sys.stdout)
handler_info.setLevel(logging.INFO)
handler_info.addFilter(lambda record: record.levelno <= logging.INFO)
# Configurar el logger
logger = logging.getLogger("mi_aplicacion")
logger.addHandler(handler_info)
# Uso
logger.info("Esto va a stdout")
logger.warning("Esto va a stderr")
logger.error("Esto también va a stderr")
Los flujos estándar de Python proporcionan una interfaz potente y flexible para la comunicación entre programas y su entorno. Dominar su uso te permitirá crear aplicaciones más robustas y adaptables a diferentes contextos de ejecución.
Lectura y escritura binaria
La manipulación de datos binarios es una habilidad fundamental para cualquier programador Python que necesite trabajar con archivos no textuales como imágenes, archivos comprimidos, o formatos personalizados. A diferencia de los archivos de texto, los archivos binarios contienen secuencias de bytes que no necesariamente representan caracteres legibles.
Python ofrece un conjunto robusto de herramientas para manejar datos binarios de forma eficiente y segura. Vamos a explorar cómo leer y escribir datos en modo binario, y algunas aplicaciones prácticas de estas técnicas.
Apertura de archivos en modo binario
Para trabajar con archivos binarios en Python, utilizamos los modos 'rb'
(lectura binaria) y 'wb'
(escritura binaria) al abrir archivos:
# Abrir un archivo para lectura binaria
with open('imagen.png', 'rb') as archivo:
contenido = archivo.read()
print(f"Tamaño del archivo: {len(contenido)} bytes")
print(f"Primeros 10 bytes: {contenido[:10]}")
# Abrir un archivo para escritura binaria
with open('nuevo.bin', 'wb') as archivo:
# Escribir bytes directamente
archivo.write(b'\x00\x01\x02\x03\x04')
Observa el prefijo b
antes de la cadena en b'\x00\x01\x02\x03\x04'
. Esto indica un literal de bytes en Python, que es diferente de una cadena de texto normal.
Trabajando con objetos bytes y bytearray
Python proporciona dos tipos principales para manejar datos binarios:
- bytes: Secuencia inmutable de bytes
- bytearray: Secuencia mutable de bytes
# Creando objetos bytes
datos1 = bytes([65, 66, 67, 68]) # A partir de una lista de enteros
datos2 = b'ABCD' # Usando un literal de bytes
datos3 = bytes.fromhex('41 42 43 44') # A partir de representación hexadecimal
print(datos1) # b'ABCD'
print(datos2) # b'ABCD'
print(datos3) # b'ABCD'
# Creando y modificando un bytearray
buffer = bytearray([0, 1, 2, 3])
buffer[0] = 255 # Podemos modificar elementos individuales
buffer.append(4) # Añadir un byte al final
print(buffer) # bytearray(b'\xff\x01\x02\x03\x04')
La diferencia clave es que bytes
es inmutable (como las cadenas de texto), mientras que bytearray
permite modificaciones (como las listas).
Lectura y escritura de estructuras binarias
Para trabajar con estructuras de datos binarias más complejas, el módulo struct
es extremadamente útil:
import struct
# Empaquetar valores en una secuencia de bytes
# '<' indica orden little-endian
# 'I' representa un entero sin signo de 4 bytes
# 'f' representa un float de 4 bytes
# '10s' representa una cadena de 10 bytes
formato = '<If10s'
datos_empaquetados = struct.pack(formato, 123456, 3.14159, b'Python\x00\x00\x00\x00')
print(f"Datos empaquetados: {datos_empaquetados.hex()}")
print(f"Tamaño: {len(datos_empaquetados)} bytes")
# Escribir en un archivo binario
with open('datos.bin', 'wb') as f:
f.write(datos_empaquetados)
# Leer desde un archivo binario
with open('datos.bin', 'rb') as f:
datos_leidos = f.read()
# Desempaquetar los valores
valores = struct.unpack(formato, datos_leidos)
print(f"Valores desempaquetados: {valores}")
print(f"Entero: {valores[0]}")
print(f"Float: {valores[1]}")
print(f"Cadena: {valores[2].rstrip(b'\x00').decode('utf-8')}")
El módulo struct
permite definir formatos precisos para convertir entre Python y representaciones binarias, lo que es esencial cuando se trabaja con formatos de archivo específicos o protocolos de red.
Manipulación de imágenes binarias
Un caso de uso común para la manipulación binaria es el procesamiento de imágenes. Veamos un ejemplo simple de cómo leer los metadatos de un archivo PNG:
def leer_dimensiones_png(ruta_archivo):
"""Lee las dimensiones de un archivo PNG."""
with open(ruta_archivo, 'rb') as f:
# Saltar la firma PNG (8 bytes)
f.read(8)
# Leer el chunk IHDR
longitud = int.from_bytes(f.read(4), byteorder='big')
tipo = f.read(4)
if tipo != b'IHDR':
raise ValueError("No se encontró el encabezado IHDR esperado")
# Leer ancho y alto (4 bytes cada uno)
ancho = int.from_bytes(f.read(4), byteorder='big')
alto = int.from_bytes(f.read(4), byteorder='big')
return ancho, alto
# Uso
try:
ruta_imagen = "ejemplo.png"
ancho, alto = leer_dimensiones_png(ruta_imagen)
print(f"Dimensiones de la imagen: {ancho}x{alto} píxeles")
except Exception as e:
print(f"Error al leer la imagen: {e}")
Este ejemplo muestra cómo podemos extraer información específica de un formato binario conocido sin necesidad de cargar todo el archivo en memoria.
Lectura y escritura parcial
A menudo, no necesitamos leer todo un archivo binario a la vez, especialmente si es grande. Python permite la lectura y escritura parcial:
# Lectura parcial
with open('archivo_grande.bin', 'rb') as f:
# Leer los primeros 100 bytes
encabezado = f.read(100)
# Saltar al byte 1000
f.seek(1000)
# Leer 50 bytes desde la posición actual
datos_medio = f.read(50)
# Ir al final menos 200 bytes (desde el final)
f.seek(-200, 2) # 2 significa relativo al final
# Leer hasta el final
datos_final = f.read()
# Escritura en posiciones específicas
with open('datos_actualizados.bin', 'r+b') as f: # r+b permite lectura y escritura
# Ir a la posición 500
f.seek(500)
# Sobrescribir 4 bytes en esa posición
f.write(b'\xFF\xFF\xFF\xFF')
El método seek()
es crucial para la manipulación de archivos binarios, ya que permite movernos a posiciones específicas dentro del archivo. El segundo argumento opcional especifica el punto de referencia:
- 0 (predeterminado): desde el inicio del archivo
- 1: desde la posición actual
- 2: desde el final del archivo
Manejo de archivos binarios grandes con memoria limitada
Cuando trabajamos con archivos binarios muy grandes, es importante evitar cargarlos completamente en memoria. Python facilita el procesamiento por bloques:
def copiar_archivo_grande(origen, destino, tamano_bloque=1024*1024):
"""
Copia un archivo grande por bloques para minimizar el uso de memoria.
Args:
origen: Ruta del archivo de origen
destino: Ruta del archivo de destino
tamano_bloque: Tamaño de cada bloque en bytes (predeterminado: 1MB)
"""
bytes_copiados = 0
with open(origen, 'rb') as f_origen:
with open(destino, 'wb') as f_destino:
while True:
bloque = f_origen.read(tamano_bloque)
if not bloque: # Fin del archivo
break
f_destino.write(bloque)
bytes_copiados += len(bloque)
# Opcional: mostrar progreso
print(f"\rCopiados {bytes_copiados/1024/1024:.2f} MB", end="")
print(f"\nCopia completada: {bytes_copiados} bytes")
# Uso
copiar_archivo_grande("video_grande.mp4", "copia_video.mp4")
Este patrón es extremadamente útil para procesar archivos de varios gigabytes sin agotar la memoria del sistema.
Trabajando con archivos comprimidos
Python permite trabajar directamente con archivos comprimidos en modo binario:
import gzip
import shutil
# Comprimir un archivo
with open('datos.txt', 'rb') as f_entrada:
with gzip.open('datos.txt.gz', 'wb') as f_salida:
shutil.copyfileobj(f_entrada, f_salida)
# Descomprimir un archivo
with gzip.open('datos.txt.gz', 'rb') as f_entrada:
with open('datos_recuperados.txt', 'wb') as f_salida:
contenido = f_entrada.read()
f_salida.write(contenido)
Además de gzip
, Python ofrece soporte para otros formatos de compresión como bz2
, lzma
y zipfile
.
Conversión entre texto y binario
A veces necesitamos convertir entre datos de texto y binarios:
# Texto a binario
texto = "¡Hola, mundo!"
datos_binarios = texto.encode('utf-8') # Codificar como UTF-8
print(datos_binarios) # b'\xc2\xa1Hola, mundo!'
# Binario a texto
texto_recuperado = datos_binarios.decode('utf-8') # Decodificar desde UTF-8
print(texto_recuperado) # ¡Hola, mundo!
# Manejo de errores de codificación
datos_incompletos = b'\xc2\xa1Hola, mund\xf3'
try:
texto = datos_incompletos.decode('utf-8')
except UnicodeDecodeError:
# Estrategia 1: Reemplazar caracteres inválidos
texto = datos_incompletos.decode('utf-8', errors='replace')
print(f"Texto con reemplazos: {texto}")
# Estrategia 2: Ignorar caracteres inválidos
texto = datos_incompletos.decode('utf-8', errors='ignore')
print(f"Texto con caracteres ignorados: {texto}")
Es importante especificar la codificación correcta al convertir entre texto y binario, especialmente cuando se trabaja con caracteres no ASCII.
Ejemplo práctico: Manipulación de un archivo de imagen
Veamos un ejemplo más completo que muestra cómo modificar una imagen a nivel binario:
def invertir_colores_bmp(ruta_entrada, ruta_salida):
"""
Invierte los colores de una imagen BMP simple.
Nota: Este ejemplo funciona solo con BMP sin compresión de 24 bits.
"""
with open(ruta_entrada, 'rb') as f:
# Leer el encabezado BMP (54 bytes en formato estándar)
encabezado = f.read(54)
# Verificar que es un archivo BMP válido
if encabezado[0:2] != b'BM':
raise ValueError("No es un archivo BMP válido")
# Crear un nuevo archivo con el mismo encabezado
with open(ruta_salida, 'wb') as f_salida:
# Escribir el encabezado sin cambios
f_salida.write(encabezado)
# Procesar los datos de píxeles (3 bytes por píxel: BGR)
while True:
# Leer un píxel (3 bytes)
pixel = f.read(3)
if not pixel or len(pixel) < 3:
break
# Invertir los valores de color (255 - valor)
pixel_invertido = bytes([255 - b for b in pixel])
# Escribir el píxel invertido
f_salida.write(pixel_invertido)
# Uso
try:
invertir_colores_bmp("imagen.bmp", "imagen_invertida.bmp")
print("Imagen procesada correctamente")
except Exception as e:
print(f"Error al procesar la imagen: {e}")
Este ejemplo muestra cómo podemos manipular datos binarios con un propósito específico, entendiendo la estructura del formato de archivo.
Consideraciones de rendimiento y seguridad
Al trabajar con archivos binarios, hay algunas consideraciones importantes:
- Rendimiento: Para archivos grandes, siempre procesa por bloques en lugar de cargar todo en memoria.
- Validación: Siempre valida los datos binarios antes de procesarlos, especialmente si provienen de fuentes externas.
- Portabilidad: Ten en cuenta el orden de bytes (endianness) cuando trabajes con valores numéricos en diferentes plataformas.
# Ejemplo de validación de datos binarios
def validar_archivo_png(ruta):
"""Valida que un archivo tenga la firma PNG correcta."""
with open(ruta, 'rb') as f:
firma = f.read(8)
return firma == b'\x89PNG\r\n\x1a\n'
# Ejemplo de manejo del orden de bytes
import struct
# Empaquetar un entero en formato big-endian y little-endian
valor = 12345
big_endian = struct.pack('>I', valor) # '>' indica big-endian
little_endian = struct.pack('<I', valor) # '<' indica little-endian
print(f"Big-endian: {big_endian.hex()}")
print(f"Little-endian: {little_endian.hex()}")
La lectura y escritura binaria en Python te permite trabajar con prácticamente cualquier formato de archivo o protocolo de comunicación. Dominar estas técnicas amplía significativamente las capacidades de tus programas, permitiéndote interactuar con sistemas de bajo nivel, formatos especializados y optimizar el rendimiento cuando trabajas con grandes volúmenes de datos.
Ejercicios de esta lección Entrada y salida avanzada
Evalúa tus conocimientos de esta lección Entrada y salida avanzada con nuestros retos de programación de tipo Test, Puzzle, Código y Proyecto con VSCode, guiados por IA.
Módulo math
Reto herencia
Excepciones
Introducción a Python
Reto variables
Funciones Python
Reto funciones
Módulo datetime
Reto acumulación
Reto estructuras condicionales
Polimorfismo
Módulo os
Reto métodos dunder
Diccionarios
Reto clases y objetos
Reto operadores
Operadores
Estructuras de control
Funciones lambda
Reto diccionarios
Reto función lambda
Encapsulación
Reto coleciones
Reto funciones auxiliares
Crear módulos y paquetes
Módulo datetime
Excepciones
Operadores
Diccionarios
Reto map, filter
Reto tuplas
Proyecto gestor de tareas CRUD
Tuplas
Variables
Tipos de datos
Conjuntos
Reto mixins
Módulo csv
Módulo json
Herencia
Análisis de datos de ventas con Pandas
Reto fechas y tiempo
Reto estructuras de iteración
Funciones
Reto comprehensions
Variables
Reto serialización
Módulo csv
Reto polimorfismo
Polimorfismo
Clases y objetos
Reto encapsulación
Estructuras de control
Importar módulos y paquetes
Módulo math
Funciones lambda
Reto excepciones
Listas
Reto archivos
Encapsulación
Reto conjuntos
Clases y objetos
Instalación de Python y creación de proyecto
Reto listas
Tipos de datos
Crear módulos y paquetes
Tuplas
Herencia
Reto acceso a sistema
Proyecto sintaxis calculadora
Importar módulos y paquetes
Clases y objetos
Módulo os
Listas
Conjuntos
Reto tipos de datos
Reto matemáticas
Módulo json
Todas las lecciones de Python
Accede a todas las lecciones de Python y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
Introducción A Python
Introducción
Instalación Y Creación De Proyecto
Introducción
Tema 2: Tipos De Datos, Variables Y Operadores
Introducción
Instalación De Python
Introducción
Tipos De Datos
Sintaxis
Variables
Sintaxis
Operadores
Sintaxis
Estructuras De Control
Sintaxis
Funciones
Sintaxis
Estructuras Control Iterativo
Sintaxis
Estructuras Control Condicional
Sintaxis
Testing Con Pytest
Sintaxis
Listas
Estructuras De Datos
Tuplas
Estructuras De Datos
Diccionarios
Estructuras De Datos
Conjuntos
Estructuras De Datos
Comprehensions
Estructuras De Datos
Clases Y Objetos
Programación Orientada A Objetos
Excepciones
Programación Orientada A Objetos
Encapsulación
Programación Orientada A Objetos
Herencia
Programación Orientada A Objetos
Polimorfismo
Programación Orientada A Objetos
Mixins Y Herencia Múltiple
Programación Orientada A Objetos
Métodos Especiales (Dunder Methods)
Programación Orientada A Objetos
Composición De Clases
Programación Orientada A Objetos
Funciones Lambda
Programación Funcional
Aplicación Parcial
Programación Funcional
Entrada Y Salida, Manejo De Archivos
Programación Funcional
Decoradores
Programación Funcional
Generadores
Programación Funcional
Paradigma Funcional
Programación Funcional
Composición De Funciones
Programación Funcional
Funciones Orden Superior Map Y Filter
Programación Funcional
Funciones Auxiliares
Programación Funcional
Reducción Y Acumulación
Programación Funcional
Archivos Comprimidos
Entrada Y Salida Io
Entrada Y Salida Avanzada
Entrada Y Salida Io
Archivos Temporales
Entrada Y Salida Io
Contexto With
Entrada Y Salida Io
Módulo Csv
Biblioteca Estándar
Módulo Json
Biblioteca Estándar
Módulo Datetime
Biblioteca Estándar
Módulo Math
Biblioteca Estándar
Módulo Os
Biblioteca Estándar
Módulo Re
Biblioteca Estándar
Módulo Random
Biblioteca Estándar
Módulo Time
Biblioteca Estándar
Módulo Collections
Biblioteca Estándar
Módulo Sys
Biblioteca Estándar
Módulo Statistics
Biblioteca Estándar
Módulo Pickle
Biblioteca Estándar
Módulo Pathlib
Biblioteca Estándar
Importar Módulos Y Paquetes
Paquetes Y Módulos
Crear Módulos Y Paquetes
Paquetes Y Módulos
Entornos Virtuales (Virtualenv, Venv)
Entorno Y Dependencias
Gestión De Dependencias (Pip, Requirements.txt)
Entorno Y Dependencias
Python-dotenv Y Variables De Entorno
Entorno Y Dependencias
Acceso A Datos Con Mysql, Pymongo Y Pandas
Acceso A Bases De Datos
Acceso A Mongodb Con Pymongo
Acceso A Bases De Datos
Acceso A Mysql Con Mysql Connector
Acceso A Bases De Datos
Novedades Python 3.13
Características Modernas
Operador Walrus
Características Modernas
Pattern Matching
Características Modernas
Instalación Beautiful Soup
Web Scraping
Sintaxis General De Beautiful Soup
Web Scraping
Tipos De Selectores
Web Scraping
Web Scraping De Html
Web Scraping
Web Scraping Para Ciencia De Datos
Web Scraping
Autenticación Y Acceso A Recursos Protegidos
Web Scraping
Combinación De Selenium Con Beautiful Soup
Web Scraping
En esta lección
Objetivos de aprendizaje de esta lección
- Comprender y aplicar la redirección de entrada y salida estándar en Python mediante gestores de contexto y clases personalizadas.
- Utilizar los objetos sys.stdin, sys.stdout y sys.stderr para gestionar flujos de datos en programas y scripts.
- Leer y escribir datos binarios en archivos, manejando tipos bytes y bytearray, y usando el módulo struct para estructuras complejas.
- Implementar técnicas para procesar archivos binarios grandes de forma eficiente y segura.
- Aplicar la manipulación binaria en casos prácticos como imágenes y archivos comprimidos, y entender la conversión entre texto y binario.