En esta sección vamos a ver distintos formatos para organizar la información en archivos, es decir de la organización lógica de los datos. Estos formatos son independientes del lenguaje de programación que utilicemos.
Los registros permiten estructurar la información de una forma que facilita su almacenamiento, recuperación y manipulación.
Supongamos que queremos crear una agenda para almacenar datos de contactos: nombre, apellido, teléfono y email. En este caso un registro será el conjunto de datos de un contacto, y los campos serán nombre, apellido, teléfono y email.
Una primera aproximación sería guardar los datos sin ningún tipo de organización, simplemente uno detrás de otro:
class Agenda:
def __init__(self, archivo):
self.archivo = archivo
def guardar_contacto(self, nombre, apellido, telefono, email):
with open(self.archivo, "a") as datos:
datos.write(nombre)
datos.write(apellido)
datos.write(telefono)
datos.write(email)agenda = Agenda("agenda.txt")
agenda.guardar_contacto("Ana", "Calle Falsa 123", "555-1234", "ana@example.com")
agenda.guardar_contacto("Bart", "Calle Falsa 123", "555-5678", "bart@example.com")with open("agenda.txt") as datos:
for linea in datos:
print(linea)AnaCalle Falsa 123555-1234ana@example.comBartCalle Falsa 123555-5678bart@example.com
Si observamos el contenido del archivo agenda.txt, vemos que los datos están todos juntos, sin ningún tipo de organización. Se ha perdido la integridad de los datos, ya que no sabemos dónde termina un dato y empieza otro. Además, la búsqueda de un contacto es muy ineficiente, ya que debemos leer todo el archivo hasta encontrarlo.
Existen varios formatos para organizar los registros en un archivo, veamos algunos de ellos.
1Registros de longitud fija con campos de longitud fija¶
Una forma de organizar los registros es asignar una longitud fija a cada campo. Por ejemplo, podemos decidir que el campo nombre tendrá 20 caracteres, el campo apellido 30 caracteres, el campo teléfono 15 caracteres y el campo email 40 caracteres. Si un dato es más corto que la longitud asignada, se rellena con espacios en blanco o nulos. Si un dato es más largo, se trunca.
import os
import struct
class Agenda:
def __init__(
self,
archivo,
len_nombre=20,
len_apellido=30,
len_telefono=15,
len_email=40,
):
self._archivo = archivo
# Formato del registro: cada campo tiene longitud fija en bytes.
# Ejemplo: "20s30s15s40s" para nombre, apellido, teléfono y email.
# En total 105 bytes por registro.
self._formato = "%ds%ds%ds%ds" % (
len_nombre,
len_apellido,
len_telefono,
len_email,
)
# Calcula la longitud total del registro en bytes.
# struct.calcsize calcula el tamaño en bytes del formato especificado.
self._len_registro = struct.calcsize(self._formato)
# Calcula la cantidad de registros presentes en la agenda.
try:
tam_archivo = os.path.getsize(archivo)
# Divide el tamaño del archivo por la longitud de cada registro.
self._cant_registros = tam_archivo // self._len_registro
except FileNotFoundError:
self._cant_registros = 0
def guardar_contacto(self, nombre, apellido, telefono="", email=""):
"""
Guarda un registro en el archivo.
Nombre y apellido son obligatorios.
"""
if not nombre or not apellido:
raise ValueError("Nombre y apellido son obligatorios")
# Abre el archivo en modo append binario. Lo crea si no existe.
with open(self._archivo, "ab") as registros:
# struct.pack convierte los datos en una secuencia de bytes
# según el formato definido. Cada campo se codifica y se
# rellena o trunca para ocupar la cantidad de bytes
# especificada.
registro = struct.pack(
self._formato,
nombre.encode(),
apellido.encode(),
telefono.encode(),
email.encode(),
)
# Escribe el registro al final del archivo.
registros.write(registro)
self._cant_registros += 1
def cantidad_registros(self):
"""Devuelve la cantidad de registros que hay en la agenda."""
return self._cant_registros
def __iter__(self):
"""Devuelve un iterador para la agenda."""
return AgendaIterator(self)A continuación definimos el iterador para la agenda:
class AgendaIterator:
"""Iterador para la agenda de registros de longitud fija"""
def __init__(self, agenda):
self._agenda = agenda
self._index = 0 # Índice del registro actual
def __iter__(self):
return self
def __next__(self):
"""
Devuelve:
dict: Un diccionario con el siguiente conjunto de datos o
valores en la iteración.
"""
# Si no quedan más registros finalizamos la iteración
if self._index >= self._agenda._cant_registros:
raise StopIteration()
with open(self._agenda._archivo, "rb") as registros:
# Calcula la posición en bytes del registro actual
posicion = self._index * self._agenda._len_registro
registros.seek(posicion)
# Lee el bloque de bytes correspondiente al registro
registro = registros.read(self._agenda._len_registro)
self._index += 1
# Verifica que se haya leído un registro completo
if len(registro) != self._agenda._len_registro:
# Si el registro está corrupto, devuelve campos vacíos
return "", "", "", ""
# struct.unpack convierte los bytes en tuplas de campos
# según el formato definido en la agenda
b_nombre, b_apellido, b_telefono, b_email = struct.unpack(
self._agenda._formato, registro
)
# Decodifica los bytes y elimina espacios/nulos extra
return (
b_nombre.decode().strip(),
b_apellido.decode().strip(),
b_telefono.decode().strip(),
b_email.decode().strip(),
)Ejemplo de uso con el iterador
agenda = Agenda("agenda_fixed.dat")
agenda.guardar_contacto("Marge", "Simpson", "555-1234", "marge@simpson.com")
agenda.guardar_contacto("Bart", "Simpson", "555-5678", "bart@simpson.com")
agenda.guardar_contacto("Homero", "Simpson", "555-8765", "homero@simpson.com")
agenda.guardar_contacto("Lisa", "Simpson") # sin teléfono ni email
print(f"Cantidad de registros: {agenda.cantidad_registros()}")
for nombre, apellido, telefono, email in agenda:
print()
print(f"{nombre} {apellido}")
print(f"Teléfono: {telefono}")
print(f"Email: {email}")Output
Cantidad de registros: 4
Marge Simpson
Teléfono: 555-1234
Email: marge@simpson.com
Bart Simpson
Teléfono: 555-5678
Email: bart@simpson.com
Homero Simpson
Teléfono: 555-8765
Email: homero@simpson.com
Lisa Simpson
Teléfono:
Email:
Si observamos el contenido del archivo agenda_fixed.dat con un editor hexadecimal, vemos que los datos están organizados en bloques de longitud fija, y cada campo ocupa la cantidad de bytes asignada, rellenando con nulos si es necesario.
with open("agenda_fixed.dat", "rb") as f:
contenido = f.read()
print(contenido)
print(f"Longitud del registro: {agenda._len_registro} bytes")
print(f"Formato del registro: {agenda._formato} (105 bytes)")
print(f"Cantidad de registros: {agenda.cantidad_registros()}")
print(
f"Cantidad de bytes en el archivo: {len(contenido)} "
f"(105 * {agenda.cantidad_registros()})"
)Output
b'Marge\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Simpson\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00555-1234\x00\x00\x00\x00\x00\x00\x00marge@simpson.com\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Bart\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Simpson\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00555-5678\x00\x00\x00\x00\x00\x00\x00bart@simpson.com\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Homero\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Simpson\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00555-8765\x00\x00\x00\x00\x00\x00\x00homero@simpson.com\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Lisa\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Simpson\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
Longitud del registro: 105 bytes
Formato del registro: 20s30s15s40s (105 bytes)
Cantidad de registros: 4
Cantidad de bytes en el archivo: 420 (105 * 4)
Este tipo de registros, no permite almacenar datos que superen la longitud asignada, ya que se truncan. Por ejemplo, si intentamos guardar un nombre con más de 20 caracteres, se perderán los caracteres adicionales. Por otro lado, si el dato es más corto que la longitud asignada, se rellena con nulos. En el archivo anterior observamos que la mayoría de los caracteres escritos son caracteres nulos "\x00", es decir la mayoría del espacio utilizado no contiene información relevante.
2Registros de longitud fija y campos de longitud variable¶
Otra forma de organizar los registros es asignar una longitud fija a cada registro, pero permitir que los campos tengan longitud variable. Para ello, se puede utilizar un delimitador para separar los campos dentro del registro. Por ejemplo, podemos utilizar el carácter "|" como delimitador. Este tipo de organización es más eficiente en términos de espacio, ya que no se desperdicia espacio si los campos son más cortos que la longitud asignada. Sin embargo, tiene la desventaja de que no se pueden almacenar registros que superen la longitud asignada, ya que se truncan.
class Agenda:
def __init__(self, archivo, campos, len_registro=100):
self._archivo = archivo
self._len_registro = len_registro
self._campos = campos # lista de nombres de campos
try:
tam_archivo = os.path.getsize(archivo)
self._cant_registros = tam_archivo // self._len_registro
except FileNotFoundError:
self._cant_registros = 0
def guardar_contacto(self, *valores):
"""
Guarda un registro en el archivo.
La cantidad de valores debe coincidir con la cantidad de campos.
"""
if len(valores) != len(self._campos):
raise ValueError("Cantidad de valores incorrecta")
if not valores[0] or not valores[1]:
raise ValueError("Nombre y apellido son obligatorios")
# Une los valores usando '|' como delimitador
registro = "|".join(str(valor) for valor in valores)
# Verifica que el registro no supere la longitud máxima
if len(registro.encode()) > self._len_registro:
raise ValueError("El registro es demasiado largo")
# Convierte el registro a bytes y lo rellena con nulos si es necesario
registro = registro.encode()
registro = registro.ljust(self._len_registro, b"\x00")
# Escribe el registro al final del archivo
with open(self._archivo, "ab") as registros:
registros.write(registro)
self._cant_registros += 1
def cantidad_registros(self):
"""Devuelve la cantidad de registros que hay en la agenda"""
return self._cant_registros
def __iter__(self):
"""Devuelve un iterador para la agenda"""
return AgendaIterator(self)A continuación definimos el iterador para la agenda:
class AgendaIterator:
"""Iterador para la agenda de registros de longitud fija y campos de
longitud variable
"""
def __init__(self, agenda):
self._agenda = agenda
self._index = 0 # Índice del registro actual
def __iter__(self):
return self
def __next__(self):
"""
Devuelve:
dict: Un diccionario con el siguiente conjunto de datos o valores
en la iteración.
"""
# Si no quedan más registros finalizamos la iteración
if self._index >= self._agenda._cant_registros:
raise StopIteration()
with open(self._agenda._archivo, "rb") as registros:
# Calcula la posición en bytes del registro actual.
# Cada registro ocupa self._agenda._len_registro bytes,
# por lo tanto la posición es índice * longitud_registro.
posicion = int(self._index * self._agenda._len_registro)
registros.seek(posicion)
registro = registros.read(self._agenda._len_registro)
self._index += 1
if len(registro) != self._agenda._len_registro:
# Registro corrupto
return dict((campo, "") for campo in self._agenda._campos)
registro = registro.rstrip(b"\x00").decode(errors="replace")
campos = registro.split("|")
# Rellenar campos faltantes con cadenas vacías
while len(campos) < len(self._agenda._campos):
campos.append("")
# Devuelve un diccionario con claves nombre de los campos
return dict(zip(self._agenda._campos, campos[: len(self._agenda._campos)]))Ejemplo de uso con el iterador
agenda = Agenda("agenda.dat", ["nombre", "apellido", "telefono", "email"])
agenda.guardar_contacto("Juan", "Pérez", "123456789", "juan@example.com")
agenda.guardar_contacto("Ana", "Gómez", "987654321", "ana@example.com")
for contacto in agenda:
print(contacto)Output
{'nombre': 'Juan', 'apellido': 'Pérez', 'telefono': '123456789', 'email': 'juan@example.com'}
{'nombre': 'Ana', 'apellido': 'Gómez', 'telefono': '987654321', 'email': 'ana@example.com'}
Si observamos el contenido del archivo agenda.dat con un editor hexadecimal, vemos que los datos están organizados en bloques de longitud fija, y cada campo está separado por el carácter "|", rellenando con nulos si es necesario.
with open("agenda.dat", "rb") as f:
contenido = f.read()
print(contenido)
print(f"Longitud del registro: {agenda._len_registro} bytes")
print(f"Cantidad de registros: {agenda.cantidad_registros()}")
print(
f"Cantidad de bytes en el archivo: {len(contenido)} "
f"({agenda._len_registro} * {agenda.cantidad_registros()})"
)Output
b'Juan|P\xc3\xa9rez|123456789|juan@example.com\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00Ana|G\xc3\xb3mez|987654321|ana@example.com\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
Longitud del registro: 100 bytes
Cantidad de registros: 2
Cantidad de bytes en el archivo: 200 (100 * 2)
3Registros de longitud variable y campos de longitud variable¶
Para implementar este tipo de registro se puede preceder cada registro con un entero que indique la longitud del registro en bytes. De esta forma, al leer el archivo, se lee primero la longitud del registro y luego se lee el registro completo. Analogamente, se puede preceder cada campo con un entero que indique la longitud del campo en bytes.
import struct
import os
class Agenda:
def __init__(self, archivo, campos):
self._archivo = archivo
self._campos = campos # lista de nombres de campos
try:
tam_archivo = os.path.getsize(archivo)
self._cant_registros = 0
with open(archivo, "rb") as f:
pos = 0
# Recorre el archivo desde el inicio hasta el final
while pos < tam_archivo:
f.seek(pos)
# Los primeros 4 bytes indican la longitud del registro
# 4 bytes para un entero sin signo (unsigned int)
len_bytes = f.read(4)
if len(len_bytes) < 4:
break # fin de archivo o registro corrupto
# Convierte los 4 bytes en un int (longitud del registro)
(len_registro,) = struct.unpack("I", len_bytes)
# Avanza la pos: 4 bytes + longitud del registro
pos += 4 + len_registro
# Incrementa el contador de registros
self._cant_registros += 1
except FileNotFoundError:
# Si el archivo no existe, no hay registros
self._cant_registros = 0
def guardar_contacto(self, *campos):
"""
Guarda un registro en el archivo.
La cantidad de campos debe coincidir con la definición.
"""
if len(campos) != len(self._campos):
raise ValueError("La cantidad de campos no coincide con la " "definición")
registro = b""
for campo in campos:
campo_bytes = campo.encode()
len_campo = len(campo_bytes)
# struct.pack("I", len_campo) convierte el entero en 4 bytes
# Luego se concatenan los 4 bytes de longitud y los bytes del campo
registro += struct.pack("I", len_campo) + campo_bytes
len_registro = len(registro)
with open(self._archivo, "ab") as registros:
# Se concatenan los 4 bytes de longitud y los bytes del registro
registros.write(struct.pack("I", len_registro))
registros.write(registro)
self._cant_registros += 1
def cantidad_registros(self):
"""Devuelve la cantidad de registros que hay en la agenda"""
return self._cant_registros
def __iter__(self):
"""Devuelve un iterador para la agenda"""
return AgendaIterator(self)A continuación definimos el iterador para la agenda:
class AgendaIterator:
"""Iterador para la agenda de registros de longitud variable"""
def __init__(self, agenda):
self._agenda = agenda
self._pos = 0
self._tam_archivo = os.path.getsize(self._agenda._archivo)
def __iter__(self):
return self
def __next__(self):
"""
Devuelve:
dict: Un diccionario con el siguiente conjunto de datos o
valores en la iteración.
"""
# Si quedan registros por leer
if self._pos >= self._tam_archivo:
raise StopIteration()
with open(self._agenda._archivo, "rb") as f:
f.seek(self._pos)
len_bytes = f.read(4)
if len(len_bytes) < 4:
raise StopIteration()
(len_registro,) = struct.unpack("I", len_bytes)
registro_bytes = f.read(len_registro)
if len(registro_bytes) < len_registro:
raise StopIteration()
self._pos += 4 + len_registro
campos = []
# offset es la posición dentro del registro
offset = 0
while (offset < len_registro) and (len(campos) < len(self._agenda._campos)):
len_campo_bytes = registro_bytes[offset : offset + 4]
if len(len_campo_bytes) < 4:
break
(len_campo,) = struct.unpack("I", len_campo_bytes)
offset += 4
campo_bytes = registro_bytes[offset : offset + len_campo]
campo = campo_bytes.decode()
campos.append(campo)
offset += len_campo
# Rellenar campos faltantes con cadenas vacías
while len(campos) < len(self._agenda._campos):
campos.append("")
return dict(zip(self._agenda._campos, campos))Ejemplo de uso con el iterador
campos = ["nombre", "apellido", "telefono", "email"]
agenda = Agenda("agenda_var.dat", campos)
agenda.guardar_contacto("Juan", "Pérez", "123456789", "juan.perez@example.com")
agenda.guardar_contacto("Ana", "Gómez", "987654321", "ana.gomez@example.com")
agenda.guardar_contacto("Homero", "Simpson", "555-8765", "")
agenda.guardar_contacto("Lisa", "Simpson", "", "lisa.simpson@example.com")
for contacto in agenda:
print(contacto)Output
{'nombre': 'Juan', 'apellido': 'Pérez', 'telefono': '123456789', 'email': 'juan.perez@example.com'}
{'nombre': 'Ana', 'apellido': 'Gómez', 'telefono': '987654321', 'email': 'ana.gomez@example.com'}
{'nombre': 'Homero', 'apellido': 'Simpson', 'telefono': '555-8765', 'email': ''}
{'nombre': 'Lisa', 'apellido': 'Simpson', 'telefono': '', 'email': 'lisa.simpson@example.com'}
Si observamos el contenido del archivo agenda_var.dat con un editor hexadecimal, vemos que los datos están organizados en bloques de longitud variable, y cada campo está precedido por un entero que indica la longitud del campo en bytes.
with open("agenda_var.dat", "rb") as f:
contenido = f.read()
print(contenido)
print(f"Cantidad de bytes en el archivo: {len(contenido)}")
print(f"Cantidad de registros: {agenda.cantidad_registros()}")Output
b'9\x00\x00\x00\x04\x00\x00\x00Juan\x06\x00\x00\x00P\xc3\xa9rez\t\x00\x00\x00123456789\x16\x00\x00\x00juan.perez@example.com7\x00\x00\x00\x03\x00\x00\x00Ana\x06\x00\x00\x00G\xc3\xb3mez\t\x00\x00\x00987654321\x15\x00\x00\x00ana.gomez@example.com%\x00\x00\x00\x06\x00\x00\x00Homero\x07\x00\x00\x00Simpson\x08\x00\x00\x00555-8765\x00\x00\x00\x003\x00\x00\x00\x04\x00\x00\x00Lisa\x07\x00\x00\x00Simpson\x00\x00\x00\x00\x18\x00\x00\x00lisa.simpson@example.com'
Cantidad de bytes en el archivo: 216
Cantidad de registros: 4
Esta forma de organizar los registros es la más flexible, ya que permite almacenar datos de cualquier longitud sin desperdiciar espacio. Sin embargo, tiene la desventaja de que el acceso a los registros es secuencial, ya que no se puede calcular la posición de un registro en función de su índice. Además, la implementación es más compleja, ya que se deben manejar las longitudes de los registros y campos.
4Otras formas de organizar registros¶
Se puede usar índices para acceder rápidamente a los registros, para lo que se crea un archivo de índices que contenga la posición de cada registro en el archivo de datos. De esta forma, se puede acceder rápidamente a un registro específico sin tener que leer todo el archivo.
Archivo de índices
Archivo de índices
Los índices permiten acelerar la búsqueda de registros, ya que se puede calcular la posición de un registro en función de su índice.
Cada una de estas formas tiene sus ventajas y desventajas, y la elección depende de las necesidades específicas de la aplicación. En general cuanto mayor flexibilidad se requiere en función del espacio ocupado, mayor es la complejidad de la implementación.
5Archivos CSV¶
Otra forma común de organizar los registros es utilizar el formato CSV (Comma-Separated Values). En este formato, cada registro se almacena en una línea del archivo, y los campos dentro del registro están separados por comas. Si un campo contiene una coma, se encierra entre comillas dobles. Si un campo contiene comillas dobles, se escapan con otra comilla doble.
Python cuenta con un módulo estándar llamado csv que facilita la lectura y escritura de archivos en formato CSV. Veamos cómo implementar la clase Agenda utilizando este formato.
import csv
import os
class Agenda:
def __init__(self, archivo, campos, separador=","):
self._archivo = archivo
self._campos = campos # lista de nombres de campos
self._cant_registros = 0
self._separador = separador
# Escribir cabecera si el archivo no existe o está vacío
if not os.path.exists(archivo) or os.path.getsize(archivo) == 0:
with open(archivo, "w", newline="") as f:
# Escribir el encabezado usando los nombres de los campos
writer = csv.DictWriter(f, fieldnames=campos, delimiter=separador)
writer.writeheader() # escribe la cabecera en la primera línea
# Contar registros (excluyendo la cabecera)
try:
with open(archivo, "r", newline="") as f:
reader = csv.DictReader(f, fieldnames=campos, delimiter=separador)
next(reader, None) # saltar cabecera
for _ in reader:
self._cant_registros += 1
except FileNotFoundError:
self._cant_registros = 0
def guardar_contacto(self, *valores):
"""
Guarda un registro. La cantidad de valores debe coincidir con
la cantidad de campos.
Se arma el registro como un diccionario: {campo: valor}
"""
if len(valores) != len(self._campos):
raise ValueError("Cantidad de valores incorrecta")
if not valores[0] or not valores[1]:
raise ValueError("Nombre y apellido son obligatorios")
registro = dict(zip(self._campos, valores)) # registro como dict
with open(self._archivo, "a", newline="") as f:
writer = csv.DictWriter(
f, fieldnames=self._campos, delimiter=self._separador
)
writer.writerow(registro) # escribe el registro como fila dict
self._cant_registros += 1
def cantidad_registros(self):
"""Devuelve la cantidad de registros que hay en la agenda"""
return self._cant_registros
def __iter__(self):
"""Devuelve un iterador para la agenda"""
return AgendaIterator(self)A continuación definimos el iterador para la agenda:
class AgendaIterator:
"""Iterador para la agenda de registros en formato CSV con cabecera"""
def __init__(self, agenda):
self._agenda = agenda
# Abrir el archivo en modo lectura
self._file = open(self._agenda._archivo, "r", newline="")
# Usar DictReader para leer registros como diccionarios
self._reader = csv.DictReader(
self._file,
fieldnames=self._agenda._campos,
delimiter=self._agenda._separador,
)
next(self._reader, None) # Saltar la cabecera
def __iter__(self):
return self
def __next__(self):
try:
# Cada registro se obtiene como un diccionario {campo: valor}
registro = next(self._reader)
# Si faltan campos, se rellenan con cadenas vacías
for campo in self._agenda._campos:
if campo not in registro or registro[campo] is None:
registro[campo] = ""
# Devuelve el registro parseado en campos
return registro
except StopIteration:
self._file.close()
raise StopIterationEjemplo de uso con el iterador
campos = ["nombre", "apellido", "telefono", "email"]
agenda = Agenda("agenda.csv", campos)
agenda.guardar_contacto("Juan", "Pérez", "123456789", "juan.perez@example.com")
agenda.guardar_contacto("Ana", "Gómez", "987654321", "ana.gomez@example.com")
agenda.guardar_contacto("Homero", "Simpson", "555-8765", "")
agenda.guardar_contacto("Lisa", "Simpson", "", "lisa.simpson@example.com")
for contacto in agenda:
print(contacto)Output
{'nombre': 'Juan', 'apellido': 'Pérez', 'telefono': '123456789', 'email': 'juan.perez@example.com'}
{'nombre': 'Ana', 'apellido': 'Gómez', 'telefono': '987654321', 'email': 'ana.gomez@example.com'}
{'nombre': 'Homero', 'apellido': 'Simpson', 'telefono': '555-8765', 'email': ''}
{'nombre': 'Lisa', 'apellido': 'Simpson', 'telefono': '', 'email': 'lisa.simpson@example.com'}
Si observamos el contenido del archivo agenda.csv, vemos que los datos están organizados en líneas, y cada campo está separado por comas. La primera línea contiene los nombres de los campos.
with open("agenda.csv", "r") as f:
contenido = f.read()
print(contenido)
print(f"Cantidad de registros: {agenda.cantidad_registros()}")Output
nombre,apellido,telefono,email
Juan,Pérez,123456789,juan.perez@example.com
Ana,Gómez,987654321,ana.gomez@example.com
Homero,Simpson,555-8765,
Lisa,Simpson,,lisa.simpson@example.com
Cantidad de registros: 4
6Comparación de formatos de registros¶
Tipo de registro | Descripción | Ventajas | Desventajas |
|---|---|---|---|
Longitud fija | Cada campo y registro ocupa una cantidad predefinida de bytes. | Acceso directo (random access) muy rápido. Implementación simple. | Desperdicio de espacio (fragmentación). Truncamiento de datos que exceden el tamaño. |
Delimitadores (CSV) | Los campos se separan por un carácter especial (coma, punto y coma) y los registros por saltos de línea. | Alta flexibilidad y legibilidad humana. Fácil de editar y compatible con muchas herramientas. | Acceso secuencial (lento para grandes volúmenes). Requiere manejo de escapes si el delimitador aparece en los datos. |
Indicadores de longitud | Cada campo o registro es precedido por un valor numérico que indica su tamaño en bytes. | Uso eficiente del espacio. Permite almacenar cualquier tipo de dato (incluyendo binarios) sin conflictos. | Implementación más compleja. Acceso secuencial. Sobrecarga de almacenamiento por los metadatos de longitud. |