如何在python中设计一个类似sqlite类的字典,它可以使用不同的字段作为“键”?

时间:2010-08-12 04:37:04

标签: python sqlite dictionary key

我有一个这样的数据结构,

"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 以简单的方式存储和查询数据。我正在尝试设计一个像对象这样的dict来存储和检索这些信息,也可以通过简单的方式与其他应用程序共享数据库。(just a plain database table for other applicationthen 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 个答案:

答案 0 :(得分:3)

sqlite是一个SQL数据库,在使用时效果最好(包含在SQLAlchemy或其他任何内容,如果你真的坚持; - )。

d[key="NAME", 'Joe']之类的语法只是非法的Python,无论你做多少包装和喘气和喘气。围绕数据库连接的简单类包装很容易,但是它永远不会给你那种语法 - 像d.fetch('Joe', key='Name')这样的东西很容易实现,但是索引与函数调用的语法非常不同,甚至在后面命名参数必须在位置之后

如果你愿意放弃你雄心勃勃的语法梦想,支持合理的Python语法,并需要帮助设计一个类来实现后者,当然可以随意问(我很快就会上床睡觉,但是我相信其他的,后来的睡眠者会渴望得到帮助; - )。

编辑:给出OP的澄清(在评论中),看起来set_key方法可以接受维护Python可接受的语法(尽管语义当然仍然是tad off,因为OP想要一个“dict-like”对象,它可能有非唯一键 - 在Python中没有这样的东西,真的......但是,我们可以近似一点,至少)。

所以,这是第一个草图(需要Python 2.6或更高版本 - 只是因为我使用collections.MutableMapping来获取其他类似dict的方法,并使用.format来格式化字符串;如果你是卡在2.5,%-formatting字符串和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(创建一个新的数据库或消灭现有的数据库)和open,它似乎比OP的更好地匹配OP的愿望(让__init__获取数据库文件路径一个描述如何打开的选项字符串它就像gdbm等模块一样 - 'r'打开只读,'c'创建或消除,'w'打开读写 - 简单当然要调整)。在传递(作为空格分隔的字符串)到create的列中,必须是一个名为ID的列(我没有太在意提高“正确”在构建和使用此类实例时可能发生的许多用户错误中的任何错误;在所有不正确的用法上都会出现错误,但对用户来说不一定是错误的。)

一旦打开(或创建)实例,它的行为尽可能接近dict,除了所有值集必须是完全正确长度的列表,而值返回是列表列表(由于奇怪的“非唯一键”问题)。例如,上面的代码在运行时打印

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

“Python-absurd”行为是d[x] = d[x] 会失败 - 因为右侧是一个列表,例如使用单个项目(列值列表),而项目分配绝对需要一个列表,例如四个项目(列值)。这种荒谬是在OP请求的语义中,并且只能通过再次彻底改变这种荒谬的必需语义来改变(例如,强制项目分配在RHS上有一个列表列表,并使用executemany代替普通{ {1}})。

键的非唯一性也使得无法猜测execute是否对应于某些数字d[x] = v表条目的键k是否意味着替换一个({1}}如果是,哪个一个?!)或所有这些条目,或者添加另一个新条目。在上面的代码中,我采用了“添加另一个条目”的解释,但是使用SQL语句n,如果REPLACE被更改为指定一些唯一性约束,则会更改“add”中的一些语义条目“to”替换条目“如果和否则会违反唯一性约束。

我会让大家玩这个代码,并反映Python映射和关系表之间的语义差距有多大,OP非常渴望桥接(显然是他对“使用”的冲击的副作用更好的语法“比SQL提供的 - 我想知道他是否已经按照我的建议查看了SqlAlchemy。”

我认为,最后,重要的一课是我在开始时所说的,在我昨天写的答案部分的第一段,我自我引用......:

  

CREATE TABLE是一个SQL数据库并且有效   使用时最好(包裹   如果你在SQLAlchemy或其他什么   真的坚持; - )。