Foros Python

Versión completa: acceso aleatorio en ficheros
Actualmente estas viendo una versión simplificada de nuestro contenido. Ver la versión completa con el formato correcto.
a ver como describo la pregunta....
Tengo un registro, tal que nombre, apellidos, etc... con una longitud fija en bytes.
Estos registros los grabo en un fichero en binario 'w+b'. Hasta aqui simple.
Ahora supongamos que tengo dos millones de registros con una longituda cada uno de 2048 bytes.

Ahora quiero acceder a un registro, por su clave/num_registro, en concreto, sin llevar el archivo a memoria. He ojeado las librerías shelve y anydbm, pero carga todo el fichero; y para ficheros muy grandes no se puede. Por una particularidad que no viene al caso no debo hacerlo en sql.

Pongo un ejemplo en pascal para que se vea lo que busco, a ver si es posible hacerlo en python con alguna librería que desconozco.


Código:
Type
   datos = record
       clave : integer;
       nombre : string[30];
       puesto : string[20];
       sueldo : real;
       estado : boolean;
   end;

Var
   valores : datos
   archivo : file of datos;

Begin
   Assign(archivo,'empleado.dat');
   ReWrite(aechivo);
   valores.clave = 1;
   ...
   valores.estado = ....
   close(archivo);
   
end

y para buscar un registro concreto solo tendría que hacer:

Código:
Seek(miFichero, num_registro);


Lo más parecido que he encontrado en python a la estructura record de pascal es namedtuples:


Código:
>>> from collections import namedtuple
>>> Persona = namedtuple('Persona', ['nombre', 'apellidos'])
>>> p = Persona('Pepe', 'Perez')
>>> p
Persona(nombre='Pepe', apellidos='Perez')
>>> p.nombre
'Pepe'
>>> datos = ['juan', 'dominguez']
>>> Persona._make(datos)
Persona(nombre='juan', apellidos='dominguez')
>>> p
Persona(nombre='Pepe', apellidos='Perez')
>>> p._asdict()
OrderedDict([('nombre', 'Pepe'), ('apellidos', 'Perez')])
>>> p._fields
('nombre', 'apellidos')
>>> getattr(p, 'apellidos')
'Perez'
Edito:
Para la estructura del registro ya he encontrado una librería que me sirve:
recordtype 1.1
[/url]
[url=https://pypi.python.org/pypi/recordtype]
Hola, ¿cómo estás?

En Python también está disponible la función file.seek(). De modo que si f es tu fichero abierto por open(), f.seek(num_registro * 2048) debería llevarte a la ubicación del registro que quieres obtener, y una subsecuente llamada a f.read(2048) te lo retornaría.

Saludos!
Hola,

Lo que me comentas ya lo habia contemplado. Primero te pongo mi borrador y luego comento los problemas:

Código:
from pyrecord import Record
# The documentation is available at https://pythonhosted.org/pyrecord/
from datetime import datetime
# para manejar fechas, que necesitaré
import pickle, os
# serializo con pickle

Person = Record.create_type("Person", "name", "email_address")
Student = Person.extend_type("Student", "courses_read", "graduation_date", graduation_date=None)
Professor = Person.extend_type("Professor", "course_taught")

# añadimos un registro. He comprobado que este registro ocupa 211 bytes

# grabar
with open("data.dump", "ab") as output:
   # necesito esta posición para el indice
   final_fichero_0 = output.seek(0,2)
   print(final_fichero_0)
   pickle.dump(john_student, output)
   with open("data.idx", "at") as indice:
       tupla_indice = john_student.name, str(final_fichero_0)+'\n'
       indice.write("***".join(tupla_indice))
   final_fichero_1 = output.seek(0,2)
   print(final_fichero_1)
   print(final_fichero_1 - final_fichero_0)
   
   
#recuperar
cuenta_registros = 0
desplazamiento = 0
with open("data.dump", "rb") as input:
   while input:
       input.seek(desplazamiento)
       datos_recup = input.read(211)
       if not datos_recup:
           break
       som = pickle.loads(datos_recup) # load es para ficheros
       print(som.name)
       cuenta_registros += 1
       desplazamiento = 211 * cuenta_registros
   
#vemos el tamaño del fichero
print(os.stat('data.dump').st_size)

Aqui como ves añado los registros serializando la clase de PyRecord, ademas creo un indice secuencial donde guardo el campo a buscar y el desplazamiento del archivo principal, asi la busqueda es más fácil (tengo que crear el arbol de busqueda, pero eso es otra historia).

El proceso de grabación y localización funciona correctamente (he hecho otro fichero que se encarga de buscar en el indice y luego con el desplazamiento voy al principal.

El problema es cuando borro algun registro. ¿dejo ese espacio en blanco? ¿borro ese registro con su espacio correspondiente?. En tal caso  tengo que regenerar todo el indice (no es realista). Y si dejo el espacio en blanco el fichero engordaría indefinidamente y sin motivo alguno.

Por eso estaba buscando alguna librería que me ahorrase todo este lío.

Ahora estoy mirando esta librería:
DiskDict
Bien, ese es el tipo de problemas que a ocurren cuando tienes una gran cantidad de datos almacenados y para eso surgen los motores de base de datos ya conocidos. Si no puedes usar SQL, puedo recomendarte ZODB o dobbin (conocidas como object databases) que pueden almacenar objetos de Python, aunque en lo personal no he ahondado en ninguna de ellas.

Saludos
Gracias por las sugerencias. Esta noche miraré esas BD.

Pero ya se me ha ocurrido una solución para mi problema.

Cuando borro un registro:
1.- dejar el espacio en blanco en el fichero principal
2.- borrar el campo de busqueda del fichero indice
3.- crear otro fichero con las direcciones que están en blanco dentro del fichero principal, con el fin de reutilizar ese espacio posteriormente.
4.- cuando se inserta un nuevo registro, reutilizo una de las direcciones vacías y borro esa dirección del fichero 3.

Casero, simple, económico y fácil.

Es que instalar un montón de Gbs de un gestor de Bases de Datos (p.ej. Postgresql) para una aplicación con un único fichero y tres campos de busqueda, me entran calambres solo de pensarlo  Cry

gracias por tu ayuda.
Perfecto. En esos casos SQLite resulta ideal ya que es realmente liviano y se incluye en la librería estándar.

Saludos!
para completar y cerrar el hilo he creado un arbol B+ en disco para manejar las inserciones/busquedas en el indice como un diccionario:

arbol["clave"] = valor

lo podeis descargar de Aqui

Es un arbol de caracter general, no es apto para red. Puede leer en multiproceso, pero no crear nuevos datos ni modificarlos  (solo en monoproceso). Se puede cachear el arbol para rapidez, y se puede recorrer en orden. Y hay una funcionalidad para limpiar la basura que se va acumulando por el uso continuo del indice.

Se adjunta tambien un fichero explicativo de como usarlo. Según las notas que tenía una parte del código fue creado por un tal Aaron W. (no guardo más referencias; solo que el codigo original proviene de 2005 en Python 2). La versión actual está en 3.6.1 - Probado en Windows y funciona en las pruebas correctamente. Tiene dos tests para probar las funcionalidades.

saludos.