Python

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ícate

Redirecció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.

CONSTRUYE TU CARRERA EN IA Y PROGRAMACIÓN SOFTWARE

Accede a +1000 lecciones y cursos con certificado. Mejora tu portfolio con certificados de superación para tu CV.

30 % DE DESCUENTO

Plan mensual

19.00 /mes

13.30 € /mes

Precio normal mensual: 19 €
63 % DE DESCUENTO

Plan anual

10.00 /mes

7.00 € /mes

Ahorras 144 € al año
Precio normal anual: 120 €
Aprende Python online

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

Python
Puzzle

Reto herencia

Python
Código

Excepciones

Python
Test

Introducción a Python

Python
Test

Reto variables

Python
Código

Funciones Python

Python
Puzzle

Reto funciones

Python
Código

Módulo datetime

Python
Test

Reto acumulación

Python
Código

Reto estructuras condicionales

Python
Código

Polimorfismo

Python
Test

Módulo os

Python
Test

Reto métodos dunder

Python
Código

Diccionarios

Python
Puzzle

Reto clases y objetos

Python
Código

Reto operadores

Python
Código

Operadores

Python
Test

Estructuras de control

Python
Puzzle

Funciones lambda

Python
Test

Reto diccionarios

Python
Código

Reto función lambda

Python
Código

Encapsulación

Python
Puzzle

Reto coleciones

Python
Proyecto

Reto funciones auxiliares

Python
Código

Crear módulos y paquetes

Python
Puzzle

Módulo datetime

Python
Puzzle

Excepciones

Python
Puzzle

Operadores

Python
Puzzle

Diccionarios

Python
Test

Reto map, filter

Python
Código

Reto tuplas

Python
Código

Proyecto gestor de tareas CRUD

Python
Proyecto

Tuplas

Python
Puzzle

Variables

Python
Puzzle

Tipos de datos

Python
Puzzle

Conjuntos

Python
Test

Reto mixins

Python
Código

Módulo csv

Python
Test

Módulo json

Python
Test

Herencia

Python
Test

Análisis de datos de ventas con Pandas

Python
Proyecto

Reto fechas y tiempo

Python
Proyecto

Reto estructuras de iteración

Python
Código

Funciones

Python
Test

Reto comprehensions

Python
Código

Variables

Python
Test

Reto serialización

Python
Proyecto

Módulo csv

Python
Puzzle

Reto polimorfismo

Python
Código

Polimorfismo

Python
Puzzle

Clases y objetos

Python
Código

Reto encapsulación

Python
Código

Estructuras de control

Python
Test

Importar módulos y paquetes

Python
Test

Módulo math

Python
Test

Funciones lambda

Python
Puzzle

Reto excepciones

Python
Código

Listas

Python
Puzzle

Reto archivos

Python
Proyecto

Encapsulación

Python
Test

Reto conjuntos

Python
Código

Clases y objetos

Python
Test

Instalación de Python y creación de proyecto

Python
Test

Reto listas

Python
Código

Tipos de datos

Python
Test

Crear módulos y paquetes

Python
Test

Tuplas

Python
Test

Herencia

Python
Puzzle

Reto acceso a sistema

Python
Proyecto

Proyecto sintaxis calculadora

Python
Proyecto

Importar módulos y paquetes

Python
Puzzle

Clases y objetos

Python
Puzzle

Módulo os

Python
Puzzle

Listas

Python
Test

Conjuntos

Python
Puzzle

Reto tipos de datos

Python
Código

Reto matemáticas

Python
Proyecto

Módulo json

Python
Puzzle

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

Python

Introducción

Instalación Y Creación De Proyecto

Python

Introducción

Tema 2: Tipos De Datos, Variables Y Operadores

Python

Introducción

Instalación De Python

Python

Introducción

Tipos De Datos

Python

Sintaxis

Variables

Python

Sintaxis

Operadores

Python

Sintaxis

Estructuras De Control

Python

Sintaxis

Funciones

Python

Sintaxis

Estructuras Control Iterativo

Python

Sintaxis

Estructuras Control Condicional

Python

Sintaxis

Testing Con Pytest

Python

Sintaxis

Listas

Python

Estructuras De Datos

Tuplas

Python

Estructuras De Datos

Diccionarios

Python

Estructuras De Datos

Conjuntos

Python

Estructuras De Datos

Comprehensions

Python

Estructuras De Datos

Clases Y Objetos

Python

Programación Orientada A Objetos

Excepciones

Python

Programación Orientada A Objetos

Encapsulación

Python

Programación Orientada A Objetos

Herencia

Python

Programación Orientada A Objetos

Polimorfismo

Python

Programación Orientada A Objetos

Mixins Y Herencia Múltiple

Python

Programación Orientada A Objetos

Métodos Especiales (Dunder Methods)

Python

Programación Orientada A Objetos

Composición De Clases

Python

Programación Orientada A Objetos

Funciones Lambda

Python

Programación Funcional

Aplicación Parcial

Python

Programación Funcional

Entrada Y Salida, Manejo De Archivos

Python

Programación Funcional

Decoradores

Python

Programación Funcional

Generadores

Python

Programación Funcional

Paradigma Funcional

Python

Programación Funcional

Composición De Funciones

Python

Programación Funcional

Funciones Orden Superior Map Y Filter

Python

Programación Funcional

Funciones Auxiliares

Python

Programación Funcional

Reducción Y Acumulación

Python

Programación Funcional

Archivos Comprimidos

Python

Entrada Y Salida Io

Entrada Y Salida Avanzada

Python

Entrada Y Salida Io

Archivos Temporales

Python

Entrada Y Salida Io

Contexto With

Python

Entrada Y Salida Io

Módulo Csv

Python

Biblioteca Estándar

Módulo Json

Python

Biblioteca Estándar

Módulo Datetime

Python

Biblioteca Estándar

Módulo Math

Python

Biblioteca Estándar

Módulo Os

Python

Biblioteca Estándar

Módulo Re

Python

Biblioteca Estándar

Módulo Random

Python

Biblioteca Estándar

Módulo Time

Python

Biblioteca Estándar

Módulo Collections

Python

Biblioteca Estándar

Módulo Sys

Python

Biblioteca Estándar

Módulo Statistics

Python

Biblioteca Estándar

Módulo Pickle

Python

Biblioteca Estándar

Módulo Pathlib

Python

Biblioteca Estándar

Importar Módulos Y Paquetes

Python

Paquetes Y Módulos

Crear Módulos Y Paquetes

Python

Paquetes Y Módulos

Entornos Virtuales (Virtualenv, Venv)

Python

Entorno Y Dependencias

Gestión De Dependencias (Pip, Requirements.txt)

Python

Entorno Y Dependencias

Python-dotenv Y Variables De Entorno

Python

Entorno Y Dependencias

Acceso A Datos Con Mysql, Pymongo Y Pandas

Python

Acceso A Bases De Datos

Acceso A Mongodb Con Pymongo

Python

Acceso A Bases De Datos

Acceso A Mysql Con Mysql Connector

Python

Acceso A Bases De Datos

Novedades Python 3.13

Python

Características Modernas

Operador Walrus

Python

Características Modernas

Pattern Matching

Python

Características Modernas

Instalación Beautiful Soup

Python

Web Scraping

Sintaxis General De Beautiful Soup

Python

Web Scraping

Tipos De Selectores

Python

Web Scraping

Web Scraping De Html

Python

Web Scraping

Web Scraping Para Ciencia De Datos

Python

Web Scraping

Autenticación Y Acceso A Recursos Protegidos

Python

Web Scraping

Combinación De Selenium Con Beautiful Soup

Python

Web Scraping

Accede GRATIS a Python y certifícate

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.