Маленькие таблицы в Python?

Скажем, у меня не более одного или двух десятков объектов с разными свойствами, например:

UID, имя, значение, цвет, тип, расположение

Я хочу иметь возможность вызывать все объекты с помощью Location = "Boston", или Type = "Primary". Классический материал типа запроса базы данных.

Большинство табличных решений (pytables, * sql) действительно переполнены для такого небольшого набора данных. Должен ли я просто перебирать все объекты и создавать отдельный словарь для каждого столбца данных (добавление значений в словари при добавлении новых объектов)?

Это создало бы так:

{'Boston': [234, 654, 234], 'Chicago': [324, 765, 342]} - где эти трехзначные записи представляют такие вещи, как UID.

Как вы можете видеть, запрос на это будет немного больно.

Любые мысли об альтернативе?

Ответы

Ответ 1

Для небольших реляционных задач мне нравится использовать встроенный Python sets.

Для примера location = 'Boston' OR type = 'Primary', если у вас были эти данные:

users = {
   1: dict(Name="Mr. Foo", Location="Boston", Type="Secondary"),
   2: dict(Name="Mr. Bar", Location="New York", Type="Primary"),
   3: dict(Name="Mr. Quux", Location="Chicago", Type="Secondary"),
   #...
}

Вы можете сделать запрос WHERE ... OR ... следующим образом:

set1 = set(u for u in users if users[u]['Location'] == 'Boston')
set2 = set(u for u in users if users[u]['Type'] == 'Primary')
result = set1.union(set2)

Или с одним выражением:

result = set(u for u in users if users[u]['Location'] == 'Boston'
                              or users[u]['Type'] == 'Primary')

Вы также можете использовать функции itertools для создания довольно эффективных запросов данных. Например, если вы хотите сделать что-то похожее на GROUP BY city:

cities = ('Boston', 'New York', 'Chicago')
cities_users = dict(map(lambda city: (city, ifilter(lambda u: users[u]['Location'] == city, users)), cities))

Вы также можете создавать индексы вручную (построить сопоставление dict Location to User ID), чтобы ускорить процесс. Если это станет слишком медленным или громоздким, я бы, вероятно, переключился на sqlite, который теперь включен в стандартную библиотеку Python (2.5).

Ответ 2

Я не думаю, что sqlite будет "overkill" - он поставляется со стандартным Python с 2,5, поэтому нет необходимости устанавливать материал, и он может создавать и обрабатывать базы данных в памяти или на локальных дисковых файлах. Действительно, как это может быть проще...? Если вы хотите, чтобы все в памяти, включая начальные значения, и хотите использовать dicts для выражения этих начальных значений, например...:

import sqlite3

db = sqlite3.connect(':memory:')
db.execute('Create table Users (Name, Location, Type)')
db.executemany('Insert into Users values(:Name, :Location, :Type)', [
   dict(Name="Mr. Foo", Location="Boston", Type="Secondary"),
   dict(Name="Mr. Bar", Location="New York", Type="Primary"),
   dict(Name="Mr. Quux", Location="Chicago", Type="Secondary"),
   ])
db.commit()
db.row_factory = sqlite3.Row

и теперь ваш крошечный "db" в памяти готов к работе. Не сложнее сделать БД в файле диска и/или прочитать начальные значения из текстового файла, CSV и т.д., Конечно.

Querying особенно гибкий, легкий и приятный, например, вы можете смешать вставку строк и замену параметров по своему желанию...:

def where(w, *a):
  c = db.cursor()
  c.execute('Select * From Users where %s' % w, *a)
  return c.fetchall()

print [r["Name"] for r in where('Type="Secondary"')]

испускает [u'Mr. Foo', u'Mr. Quux'], как и более элегантный, но эквивалентный

print [r["Name"] for r in where('Type=?', ["Secondary"])]

и ваш желаемый запрос:

print [r["Name"] for r in where('Location="Boston" or Type="Primary"')]

и т.д.. Серьезно - что не нравится?

Ответ 3

Если это действительно небольшой объем данных, я бы не стал беспокоиться об индексе и, возможно, просто написал вспомогательную функцию:

users = [
   dict(Name="Mr. Foo", Location="Boston", Type="Secondary"),
   dict(Name="Mr. Bar", Location="New York", Type="Primary"),
   dict(Name="Mr. Quux", Location="Chicago", Type="Secondary"),
   ]

def search(dictlist, **kwargs):
   def match(d):
      for k,v in kwargs.iteritems():
         try: 
            if d[k] != v: 
               return False
         except KeyError:
            return False
      return True

   return [d for d in dictlist if match(d)] 

Который разрешит подобные запросы:

result = search(users, Type="Secondary")