`open()` y modos de apertura: texto y binario en Python

Cuando Python abre un archivo, no solo conecta tu código con bytes en disco — decide cómo interpretar esos bytes. Esa decisión, tomada en el momento de llamar a open(), lo cambia todo: si vas a leer texto o datos binarios, si vas a sobreescribir o acumular, si el archivo debe existir o no. Entender los parámetros de open() es entender cómo Python habla con el filesystem.

La firma completa que te importa es esta:

open(ruta, mode='r', encoding='utf-8', errors='strict', newline=None)

El parámetro mode combina dos decisiones independientes: qué operación quieres hacer y cómo interpretar los datos. Los modos de operación son 'r' (lectura), 'w' (escritura con truncado — borra el contenido existente al abrir), 'a' (append, escribe al final sin borrar nada) y 'x' (crear nuevo, lanza FileExistsError si ya existe). El modificador 'b' cambia el canal de texto a modo binario, donde Python no toca los bytes ni intenta decodificarlos. El modificador '+' habilita lectura y escritura simultánea sobre el mismo file object.

¿Por qué 'w' trunca en lugar de sobreescribir desde el principio? Porque a nivel de sistema operativo, abrir con truncado es la operación atómica natural: el SO marca el archivo como vacío antes de que escribas nada. Sobreescribir byte a byte sería más lento y dejaría basura al final si el nuevo contenido es más corto.

El parámetro encoding merece atención especial. Si no lo especificas, Python usa locale.getpreferredencoding(False), que en Windows puede ser 'cp1252', en macOS 'utf-8', y en Linux depende de las variables de entorno del sistema. Tu código funcionará en tu máquina y fallará en producción de otra persona. Siempre escribe encoding='utf-8' explícitamente — es la única forma de garantizar comportamiento portable.

El parámetro newline='' existe porque Windows usa \r\n como fin de línea mientras Unix usa \n. En modo texto, Python hace traducción automática: al leer convierte \r\n\n, y al escribir hace lo contrario en Windows. Cuando procesas CSVs con el módulo csv, esa traducción interfiere con la del propio módulo, por eso csv.writer te pide abrir con newline='' para desactivarla.

El objeto que devuelve open() es un file object — tiene métodos de lectura y escritura, y es un context manager, lo que significa que puede usarse con with para garantizar que el archivo se cierra aunque ocurra una excepción.

from pathlib import Path

# Datos de ejemplo que vamos a trabajar
sample_path = Path("notas.txt")

# ── Escritura inicial ──────────────────────────────────────────────────
with open(sample_path, mode="w", encoding="utf-8") as f:
    f.write("línea uno\n")
    f.write("línea dos\n")
    f.write("línea tres\n")

# ── Lectura completa de golpe ──────────────────────────────────────────
with open(sample_path, mode="r", encoding="utf-8") as f:
    contenido = f.read()          # string con todo el archivo
    print(repr(contenido))

# ── Lectura línea a línea (útil con archivos grandes) ──────────────────
with open(sample_path, mode="r", encoding="utf-8") as f:
    primera = f.readline()        # lee hasta el primer \n, lo incluye
    segunda = f.readline()
    print(repr(primera))
    print(repr(segunda))

# ── Lista de líneas ────────────────────────────────────────────────────
with open(sample_path, mode="r", encoding="utf-8") as f:
    lineas = f.readlines()        # lista; cada elemento termina en \n
    print(lineas)

# ── Append: agrega sin borrar lo existente ────────────────────────────
with open(sample_path, mode="a", encoding="utf-8") as f:
    f.write("línea cuatro\n")

# ── Modo 'x': falla si el archivo ya existe ───────────────────────────
nuevo_path = Path("config.txt")
try:
    with open(nuevo_path, mode="x", encoding="utf-8") as f:
        f.write("primera versión\n")
except FileExistsError:
    print(f"{nuevo_path} ya existe, no se sobreescribe")

# ── Modo binario: bytes puros, sin encoding ───────────────────────────
png_header = bytes([0x89, 0x50, 0x4E, 0x47])  # primeros bytes de un PNG
with open("header.bin", mode="wb") as f:       # no hay encoding en modo 'b'
    f.write(png_header)

with open("header.bin", mode="rb") as f:
    datos = f.read()
    print(datos.hex())            # '89504e47'

# ── Lectura/escritura simultánea con 'r+' ─────────────────────────────
with open(sample_path, mode="r+", encoding="utf-8") as f:
    original = f.read()
    f.seek(0)                     # volver al inicio antes de escribir
    f.write(original.upper())
    f.truncate()                  # elimina cualquier byte sobrante al final

# Limpieza
sample_path.unlink(missing_ok=True)
nuevo_path.unlink(missing_ok=True)
Path("header.bin").unlink(missing_ok=True)

Qué hace cada decisión

El bloque de escritura usa mode="w": si notas.txt ya existía, quedó vacío antes de escribir la primera línea. No hay warning, no hay confirmación — simplemente se borra. Si eso no es lo que quieres, usa 'a' o 'x'.

f.read() carga el archivo completo en memoria como un solo string. Es conveniente para archivos pequeños, pero con un log de 2 GB te quedas sin RAM. f.readline() avanza el cursor interno del file object una línea cada vez — la posición se recuerda entre llamadas dentro del mismo bloque with. f.readlines() es azúcar sobre un loop que llama a readline() hasta EOF y devuelve la lista completa; fíjate que los \n quedan incluidos en cada elemento.

El modo 'x' es el modo más seguro para crear archivos de configuración o tokens: si alguien ya creó el archivo antes que tú (race condition o bug), prefieres fallar con excepción que sobreescribirlo silenciosamente.

En el ejemplo binario no hay parámetro encoding — pasarlo sería un ValueError. En modo 'b', write() recibe bytes y read() devuelve bytes. No hay traducción de ningún tipo.

El ejemplo con 'r+' muestra la trampa clásica: cuando abres con modo lectura-escritura y quieres reescribir desde el principio, tienes que llamar a f.seek(0) manualmente para mover el cursor al inicio, y luego f.truncate() para eliminar cualquier byte que sobre si el nuevo contenido es más corto que el original.

Errores que debes conocer

Error: omitir encoding y que el código falle en otra máquina porque el encoding por defecto varía según el sistema operativo.

# ❌ Wrong
with open("datos.txt", "r") as f:
    texto = f.read()  # en Windows puede decodificarse con cp1252

# ✅ Right
with open("datos.txt", "r", encoding="utf-8") as f:
    texto = f.read()

Especificar encoding='utf-8' hace que el comportamiento sea idéntico en cualquier plataforma, independientemente del locale del sistema.


Error: usar 'w' cuando la intención es añadir contenido, perdiendo todo lo que había en el archivo.

# ❌ Wrong
with open("log.txt", "w", encoding="utf-8") as f:
    f.write("nuevo evento\n")   # borra todo el historial anterior

# ✅ Right
with open("log.txt", "a", encoding="utf-8") as f:
    f.write("nuevo evento\n")   # se agrega al final

'a' posiciona el cursor al final antes de cada escritura; 'w' trunca el archivo a cero bytes en el momento de open().


Error: pasar un string a write() en modo binario (o bytes en modo texto).

# ❌ Wrong
with open("datos.bin", "wb") as f:
    f.write("hola")   # TypeError: a bytes-like object is required

# ✅ Right
with open("datos.bin", "wb") as f:
    f.write("hola".encode("utf-8"))   # b'hola'

En modo binario, write() espera un objeto bytes o bytearray; la conversión explícita con .encode() deja claro qué encoding estás usando para esa serialización.

81

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio