Python: ¿base de datos de objetos en memoria que soporta indexación?
database data-munging (9)
¿Qué hay de usar una base de datos SQLite en memoria a través del módulo de biblioteca estándar de sqlite3 , usando el valor especial :memory:
para la conexión? Si no desea escribir sus declaraciones SQL, siempre puede usar un ORM, como SQLAlchemy , para acceder a una base de datos SQLite en memoria.
EDITAR : Noté que declaró que los valores pueden ser objetos de Python, y también que debe evitar la serialización. Requerir que se almacenen objetos Python arbitrarios en una base de datos también requiere la serialización.
¿Puedo proponer una solución práctica si debe mantener esos dos requisitos? ¿Por qué no usar los diccionarios de Python como índices en su colección de diccionarios de Python? Parece que tendrá necesidades idiosincrásicas para construir cada uno de sus índices; descubra en qué valores va a consultar, luego escriba una función para generar e indexar para cada uno. Los valores posibles para una clave en su lista de dictados serán las claves para un índice; Los valores del índice serán una lista de diccionarios. Consulta el índice dando el valor que estás buscando como clave.
import collections
import itertools
def make_indices(dicts):
color_index = collections.defaultdict(list)
age_index = collections.defaultdict(list)
for d in dicts:
if ''favorite_color'' in d:
color_index[d[''favorite_color'']].append(d)
if ''age'' in d:
age_index[d[''age'']].append(d)
return color_index, age_index
def make_data_dicts():
...
data_dicts = make_data_dicts()
color_index, age_index = make_indices(data_dicts)
# Query for those with a favorite color is simply values
with_color_dicts = list(
itertools.chain.from_iterable(color_index.values()))
# Query for people over 16
over_16 = list(
itertools.chain.from_iterable(
v for k, v in age_index.items() if age > 16)
)
Estoy haciendo una recopilación de datos que sería un poco más simple si pudiera pegar un montón de diccionarios en una base de datos en memoria, y luego ejecutar consultas contra ella.
Por ejemplo, algo como:
people = db([
{"name": "Joe", "age": 16},
{"name": "Jane", "favourite_color": "red"},
])
over_16 = db.filter(age__gt=16)
with_favorite_colors = db.filter(favorite_color__exists=True)
Sin embargo, hay tres factores de confusión:
- Algunos de los valores serán objetos de Python, y la serialización está fuera de la cuestión (demasiado lento, rompe la identidad). Por supuesto, podría solucionar esto (p. Ej., Almacenando todos los elementos en una lista grande, luego serializando sus índices en esa lista ... Pero eso podría tomar un poco de cuidado).
- Habrá miles de datos, y ejecutaré operaciones pesadas de búsqueda (como recorridos de gráficos) contra ellos, por lo que debe ser posible realizar consultas eficientes (es decir, indexadas).
- Como en el ejemplo, los datos no están estructurados , por lo que los sistemas que me requieren para predefinir un esquema serían complicados.
Entonces, ¿existe tal cosa? ¿O necesitaré guardar algo juntos?
Comencé a desarrollar uno ayer y aún no está publicado. Indexa tus objetos y te permite ejecutar consultas rápidas. Todos los datos se guardan en la memoria RAM y estoy pensando en métodos inteligentes de carga y guardado. Para propósitos de prueba se está cargando y guardando a través de cPickle.
Házme saber si sigues interesado.
Debería ser posible hacer lo que se quiere hacer solo con isinstance (), hasattr (), getattr () y setattr ().
Sin embargo, las cosas se van a complicar bastante antes de que termines.
Supongo que uno podría almacenar todos los objetos en una lista grande, luego ejecutar una consulta en cada objeto, determinar qué es y buscar un atributo o valor dado, luego devolver el valor y el objeto como una lista de tuplas. Entonces usted podría ordenar sus valores de retorno con bastante facilidad. copy.deepcopy será tu mejor amigo y tu peor enemigo.
¡Suena divertido! ¡Buena suerte!
En cuanto a la "identidad", cualquier cosa que sea hashable debería poder comparar, para hacer un seguimiento de la identidad del objeto.
Base de datos de objetos de Zope (ZODB): http://www.zodb.org/
PyTables funciona bien: http://www.pytables.org/moin
También Metakit para Python funciona bien: http://equi4.com/metakit/python.html
supports columns, and sub-columns but not unstructured data
Investigue "Procesamiento de secuencias", si sus conjuntos de datos son extremadamente grandes, esto puede ser útil: http://www.trinhhaianh.com/stream.py/
Cualquier base de datos en memoria que pueda ser serializada (escrita en disco) tendrá su problema de identidad. Sugeriría representar los datos que desea almacenar como tipos nativos (lista, dict) en lugar de objetos si es posible.
Tenga en cuenta que NumPy fue diseñado para realizar operaciones complejas en estructuras de datos en memoria, y podría ser parte de su solución si decide rodar la suya propia.
Escribí un módulo simple llamado Jsonstore que resuelve (2) y (3). Así es como iría tu ejemplo:
from jsonstore import EntryManager
from jsonstore.operators import GreaterThan, Exists
db = EntryManager('':memory:'')
db.create(name=''Joe'', age=16)
db.create({''name'': ''Jane'', ''favourite_color'': ''red''}) # alternative syntax
db.search({''age'': GreaterThan(16)})
db.search(favourite_color=Exists()) # again, 2 different syntaxes
La única solución que conozco es un paquete que encontré hace unos años en PyPI, PyDbLite . Está bien, pero hay algunos problemas:
- Todavía quiere serializar todo en el disco, como un archivo pickle. Pero eso fue lo suficientemente simple como para que yo lo arrancara. (También es innecesario. Si los objetos insertados son serializables, también lo es la colección en su conjunto.)
- El tipo de registro básico es un diccionario, en el que inserta sus propios metadatos, dos
__id__
bajo las claves__id__
y__version__
. - La indexación es muy simple, basada solo en el valor del diccionario de registro. Si desea algo más complicado, como el atributo de un objeto en el registro, tendrá que codificarlo usted mismo. (Algo que me propuse hacer yo mismo, pero nunca lo logré).
El autor parece estar trabajando en ello de vez en cuando. Hay algunas características nuevas de cuando lo usé, incluida una buena sintaxis para consultas complejas.
Suponiendo que extraiga el decapado (y puedo decirle lo que hice), su ejemplo sería (código no probado):
from PyDbLite import Base
db = Base()
db.create("name", "age", "favourite_color")
# You can insert records as either named parameters
# or in the order of the fields
db.insert(name="Joe", age=16, favourite_color=None)
db.insert("Jane", None, "red")
# These should return an object you can iterate over
# to get the matching records. These are unindexed queries.
#
# The first might throw because of the None in the second record
over_16 = db("age") > 16
with_favourite_colors = db("favourite_color") != None
# Or you can make an index for faster queries
db.create_index("favourite_color")
with_favourite_color_red = db._favourite_color["red"]
Esperemos que sea suficiente para empezar.
No estoy seguro de si cumple con todos sus requisitos, pero vale la pena probar TinyDB (que utiliza almacenamiento en memoria):
>>> from tinydb import TinyDB, Query
>>> from tinydb.storages import MemoryStorage
>>> db = TinyDB(storage=MemoryStorage)
>>> db.insert({''name'': ''John'', ''age'': 22})
>>> User = Query()
>>> db.search(User.name == ''John'')
[{''name'': ''John'', ''age'': 22}]
Su simplicidad y su potente motor de consulta lo convierten en una herramienta muy interesante para algunos casos de uso. Vea http://tinydb.readthedocs.io/ para más detalles.
Si está dispuesto a evitar la serialización, MongoDB podría funcionar para usted. PyMongo proporciona una interfaz casi idéntica a la que usted describe. Si decide serializar, el hit no será tan malo ya que Mongodb está mapeado en memoria.
Si la solución de la base de datos en memoria termina siendo demasiado trabajo, aquí hay un método para filtrarlo usted mismo que le puede resultar útil.
La función get_filter
toma argumentos para definir cómo desea filtrar un diccionario y devuelve una función que se puede pasar a la función de filter
integrada para filtrar una lista de diccionarios.
import operator
def get_filter(key, op=None, comp=None, inverse=False):
# This will invert the boolean returned by the function ''op'' if ''inverse == True''
result = lambda x: not x if inverse else x
if op is None:
# Without any function, just see if the key is in the dictionary
return lambda d: result(key in d)
if comp is None:
# If ''comp'' is None, assume the function takes one argument
return lambda d: result(op(d[key])) if key in d else False
# Use ''comp'' as the second argument to the function provided
return lambda d: result(op(d[key], comp)) if key in d else False
people = [{''age'': 16, ''name'': ''Joe''}, {''name'': ''Jane'', ''favourite_color'': ''red''}]
print filter(get_filter("age", operator.gt, 15), people)
# [{''age'': 16, ''name'': ''Joe''}]
print filter(get_filter("name", operator.eq, "Jane"), people)
# [{''name'': ''Jane'', ''favourite_color'': ''red''}]
print filter(get_filter("favourite_color", inverse=True), people)
# [{''age'': 16, ''name'': ''Joe''}]
Esto es bastante fácil de extender a un filtrado más complejo, por ejemplo, para filtrar según si un valor es compatible con una expresión regular:
p = re.compile("[aeiou]{2}") # matches two lowercase vowels in a row
print filter(get_filter("name", p.search), people)
# [{''age'': 16, ''name'': ''Joe''}]