Как я могу создать DICT как класс sqlite в Python, который может использовать другое поле в качестве «ключа»? - PullRequest
2 голосов
/ 12 августа 2010

У меня такая структура данных,

"ID  NAME  BIRTH     AGE    SEX"
=================================
1   Joe    01011980  30     M
2   Rose   12111986  24     F
3   Tom    31121965  35     M
4   Joe    15091990  20     M  

Я хочу использовать python + sqlite для простого хранения и запроса данных.Я пытаюсь спроектировать объект, похожий на диктовку, для хранения и извлечения этой информации, также база данных может совместно использоваться с другим приложением . (just a plain database table for other application, then the pickle and ySerial like object should not fit for it.)

Например:

d = mysqlitedict.open('student_table')  
 d['1'] = ["Joe","01011980","30","M"]    
 d['2'] = ["Rose","12111986","24","F"]

Это может быть разумно, потому что я могу использовать __setitem__(), чтобы получить его, если "ID" в качестве ключа и оставшаяся часть в качестве значения этого объекта, похожего на dict.

Проблема в том, что если я хочу использовать другое поле в качестве ключа семантически, возьмем, к примеру, «NAME»:

 d['Joe'] = ["1","01011980","30","M"] 

Это будет проблемой, потому что объект, похожий на dict, должен иметьпара ключ / значение семантически, так как теперь «ID» является ключом, «NAME» не может переопределить здесь ключ.

Тогда мой вопрос, могу ли я спроектировать свой класс, тогда я могу сделать так?

 d[key="NAME", "Joe"] = ["1","01011980","30","M"] 
 d[key="ID",'1'] = ["Joe","01011980","30","M"]  

 d.update(key = "ID", {'1':["Joe","01011980","30","M"]})

>>>d[key="NAME", 'Joe']
["1","Joe","01011980","30","M"]
["1","Joe","15091990","20","M"]

>>>d.has_key(key="NAME", 'Joe']
True

Буду признателен за любой ответ!

KC

1 Ответ

3 голосов
/ 12 августа 2010

sqlite является базой данных SQL и работает намного лучше, когда используется как таковой (обернуто в SQLAlchemy или что-то еще, если вы действительно настаиваете; -).

Синтаксис, такой как d[key="NAME", 'Joe'], является просто недопустимым Python, независимо от того, сколько вы можете использовать для переноса, пыхтения и пыхтения. Простая оболочка класса для соединения с БД проста, но она никогда не даст вам такой синтаксис - что-то вроде d.fetch('Joe', key='Name') достаточно просто достичь, но индексирование имеет очень другой синтаксис от вызовов функций, и даже в последних именованных аргументах необходимо следуйте за позиционными.

Если вы готовы отказаться от своих амбициозных мечтаний о синтаксисе в пользу разумного синтаксиса Python и вам нужна помощь в разработке класса для реализации последнего, не стесняйтесь спрашивать, конечно (я скоро ухожу спать, но Я уверен, что другие, позже спящие будут готовы помочь; -).

Редактировать : учитывая пояснения ОП (в комментарии), похоже, что set_key метод приемлем для поддержки синтаксиса, приемлемого для Python (хотя семантика, конечно, все равно будет немного, так как OP хочет «подобный диктату» объект, который может иметь неуникальные ключи - такого нет в Python, на самом деле ... но мы можем немного приблизить его, по крайней мере).

Итак, вот самый первый набросок (требуется Python 2.6 или выше - просто потому, что я использовал collections.MutableMapping для получения других dict-подобных методов и .format для форматирования строк; если вы застряли в 2.5, % -форматирование строк и UserDict.DictMixin будет работать вместо):

import collections
import sqlite3

class SqliteDict(collections.MutableMapping):
  @classmethod
  def create(cls, path, columns):
    conn = sqlite3.connect(path)
    conn.execute('DROP TABLE IF EXISTS SqliteDict')
    conn.execute('CREATE TABLE SqliteDict ({0})'.format(','.join(columns.split())))
    conn.commit()
    return cls(conn)

  @classmethod
  def open(cls, path):
    conn = sqlite3.connect(path)
    return cls(conn)

  def __init__(self, conn):
    # looks like for sime weird reason you want str, not unicode, when feasible, so...:
    conn.text_factory = sqlite3.OptimizedUnicode
    c = conn.cursor()
    c.execute('SELECT * FROM SqliteDict LIMIT 0')
    self.cols = [x[0] for x in c.description]
    self.conn = conn
    # start with a keyname (==column name) of `ID`
    self.set_key('ID')

  def set_key(self, key):
    self.i = self.cols.index(key)
    self.kn = key

  def __len__(self):
    c = self.conn.cursor()
    c.execute('SELECT COUNT(*) FROM SqliteDict')
    return c.fetchone()[0]

  def __iter__(self):
    c = self.conn.cursor()
    c.execute('SELECT * FROM SqliteDict')
    while True:
      result = c.fetchone()
      if result is None: break
      k = result.pop(self.i)
      return k, result

  def __getitem__(self, k):
    c = self.conn.cursor()
    # print 'doing:', 'SELECT * FROM SqliteDict WHERE {0}=?'.format(self.kn)
    # print ' with:', repr(k)
    c.execute('SELECT * FROM SqliteDict WHERE {0}=?'.format(self.kn), (k,))
    result = [list(r) for r in c.fetchall()]
    # print ' resu:', repr(result)
    for r in result: del r[self.i]
    return result

  def __contains__(self, k):
    c = self.conn.cursor()
    c.execute('SELECT * FROM SqliteDict WHERE {0}=?'.format(self.kn), (k,))
    return c.fetchone() is not None

  def __delitem__(self, k):
    c = self.conn.cursor()
    c.execute('DELETE FROM SqliteDict WHERE {0}=?'.format(self.kn), (k,))
    self.conn.commit()

  def __setitem__(self, k, v):
    r = list(v)
    r.insert(self.i, k)
    if len(r) != len(self.cols):
      raise ValueError, 'len({0}) is {1}, must be {2} instead'.format(r, len(r), len(self.cols))
    c = self.conn.cursor()
    # print 'doing:', 'REPLACE INTO SqliteDict VALUES({0})'.format(','.join(['?']*len(r)))
    # print ' with:', r
    c.execute('REPLACE INTO SqliteDict VALUES({0})'.format(','.join(['?']*len(r))), r)
    self.conn.commit()

  def close(self):
    self.conn.close()


def main():
  d = SqliteDict.create('student_table', 'ID NAME BIRTH AGE SEX')
  d['1'] = ["Joe", "01011980", "30", "M"]    
  d['2'] = ["Rose", "12111986", "24", "F"]
  print len(d), 'items in table created.'
  print d['2']
  print d['1']
  d.close()

  d = SqliteDict.open('student_table')
  d.set_key('NAME')
  print len(d), 'items in table opened.'
  print d['Joe']


if __name__ == '__main__':
  main()

Класс не предназначен для непосредственного создания экземпляра (хотя это можно сделать путем передачи открытого соединения sqlite3 к БД с соответствующей таблицей SqliteDict), но с помощью двух методов класса create (для создания нового DB или стереть существующий) и open, который, кажется, соответствует желаниям ОП лучше, чем альтернатива (пусть __init__ выберет путь к файлу БД и строку опции, описывающую, как ее открыть, точно так же как модули, такие как gdbm дубль - 'r', чтобы открыть только для чтения, 'c', чтобы создать или уничтожить, 'w', чтобы открыть для чтения-записи - легко настроить, конечно). Среди столбцов, переданных (как строка, разделенная пробелами) в create, должен быть именем с именем ID (я не особо заботился о том, чтобы выдавать «правильные» ошибки для любой из много, много пользовательских ошибок, которые могут возникнуть при создании и использовании экземпляров этого класса; ошибки будут возникать при любом неправильном использовании, но не обязательно очевидные для пользователя).

Как только экземпляр открывается (или создается), он ведет себя как можно ближе к диктату, за исключением того, что все установленные значения должны быть списками точно правильной длины, в то время как значения , возвращаемые , являются списками списки (из-за странной проблемы «неуникального ключа»). Например, приведенный выше код при запуске печатает

2 items in table created.
[['Rose', '12111986', '24', 'F']]
[['Joe', '01011980', '30', 'M']]
2 items in table opened.
[['1', '01011980', '30', 'M']]

Поведение "Pythonically абсурд" состоит в том, что d[x] = d[x] не сможет потерпеть неудачу - потому что правая часть списка, например. с одним элементом (который представляет собой список значений столбца), в то время как для назначения элемента абсолютно необходим список с, например, четыре элемента (значения столбца). Этот абсурд содержится в запрошенной семантике OP и может быть изменен только путем радикального изменения такой абсурдной требуемой семантики (например, принуждение присвоения элемента иметь список списков в RHS и использование executemany вместо простого execute ).

Неединственность ключей также делает невозможным угадывание, если d[x] = v для ключа k, который соответствует некоторому числу n записей таблицы, предназначен для замены одного (и если это так, который один ?!) или все эти записи, или вместо этого добавьте еще одну новую запись. В приведенном выше коде я принял интерпретацию «добавить еще одну запись», но с оператором SQL REPLACE, который при изменении CREATE TABLE для указания некоторых ограничений уникальности изменит семантику с «добавить запись» на « заменить записи ", если и когда ограничения уникальности были бы иначе нарушены.

Я позволю вам всем поиграть с этим кодом и показать, насколько огромен семантический разрыв между отображениями Python и реляционными таблицами, что OP отчаянно стремится к преодолению (очевидно, как побочный эффект от его желания "использовать"более приятный синтаксис, чем позволяет SQL - интересно, если бы он посмотрел на SqlAlchemy, как я рекомендовал).

Я думаю, в конце концов, важный урок - то, что я изложил прямо вначнем с того, что в первом абзаце той части ответа, которую я написал вчера, и я сам цитирую ...:

sqlite - это база данных SQL, и она работает намного лучше, когда используется как таковая(завернутый в SQLAlchemy или что-то еще, если вы действительно настаиваете; -).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...