我的场景如下:我有一个数据表(少数字段,少于一百行),我在程序中广泛使用。我还需要这些数据是持久的,因此我将其保存为CSV并在启动时加载它。我选择不使用数据库,因为每个选项(甚至是SQLite)对我的简单要求来说都是过度的(同样 - 我希望能够以简单的方式离线编辑值,没有什么比记事本简单)。
假设我的数据看起来如下(在文件中,它的逗号分隔没有标题,这只是一个例子):
Row | Name | Year | Priority
------------------------------------
1 | Cat | 1998 | 1
2 | Fish | 1998 | 2
3 | Dog | 1999 | 1
4 | Aardvark | 2000 | 1
5 | Wallaby | 2000 | 1
6 | Zebra | 2001 | 3
注意:
我对数据做的事情:
我知道SQL“哭”......
我正在试图弄清楚什么是数据结构的最佳选择。以下是我看到的几个选择:
行列表列表:
a = []
a.append( [1, "Cat", 1998, 1] )
a.append( [2, "Fish", 1998, 2] )
a.append( [3, "Dog", 1999, 1] )
...
列列表列表(显然有一个add_row等API):
a = []
a.append( [1, 2, 3, 4, 5, 6] )
a.append( ["Cat", "Fish", "Dog", "Aardvark", "Wallaby", "Zebra"] )
a.append( [1998, 1998, 1999, 2000, 2000, 2001] )
a.append( [1, 2, 1, 1, 1, 3] )
列的列表列表(可以创建常量来替换字符串键):
a = {}
a['ID'] = [1, 2, 3, 4, 5, 6]
a['Name'] = ["Cat", "Fish", "Dog", "Aardvark", "Wallaby", "Zebra"]
a['Year'] = [1998, 1998, 1999, 2000, 2000, 2001]
a['Priority'] = [1, 2, 1, 1, 1, 3]
字典,键是元组(行,字段):
Create constants to avoid string searching
NAME=1
YEAR=2
PRIORITY=3
a={}
a[(1, NAME)] = "Cat"
a[(1, YEAR)] = 1998
a[(1, PRIORITY)] = 1
a[(2, NAME)] = "Fish"
a[(2, YEAR)] = 1998
a[(2, PRIORITY)] = 2
...
我确信还有其他方法......但是当涉及到我的要求(复杂的排序和计数)时,每种方式都有缺点。
推荐的方法是什么?
编辑:
澄清一下,表现对我来说不是一个主要问题。因为表格很小,我相信几乎每一个操作都会在几毫秒的范围内,这对我的应用程序来说不是一个问题。
答案 0 :(得分:75)
在内存中有一个需要查找,排序和任意聚合的“表”确实会调用SQL。你说过你尝试过SQLite,但是你是否意识到SQLite可以使用仅内存数据库?
connection = sqlite3.connect(':memory:')
然后,您可以使用SQLite的所有功能在内存中创建/删除/查询/更新表,并在完成后不留下任何文件。从Python 2.5开始,sqlite3
位于标准库中,因此它并非真正“过度杀伤”IMO。
以下是如何创建和填充数据库的示例:
import csv
import sqlite3
db = sqlite3.connect(':memory:')
def init_db(cur):
cur.execute('''CREATE TABLE foo (
Row INTEGER,
Name TEXT,
Year INTEGER,
Priority INTEGER)''')
def populate_db(cur, csv_fp):
rdr = csv.reader(csv_fp)
cur.executemany('''
INSERT INTO foo (Row, Name, Year, Priority)
VALUES (?,?,?,?)''', rdr)
cur = db.cursor()
init_db(cur)
populate_db(cur, open('my_csv_input_file.csv'))
db.commit()
如果你真的不想使用SQL,你应该使用一个字典列表:
lod = [ ] # "list of dicts"
def populate_lod(lod, csv_fp):
rdr = csv.DictReader(csv_fp, ['Row', 'Name', 'Year', 'Priority'])
lod.extend(rdr)
def query_lod(lod, filter=None, sort_keys=None):
if filter is not None:
lod = (r for r in lod if filter(r))
if sort_keys is not None:
lod = sorted(lod, key=lambda r:[r[k] for k in sort_keys])
else:
lod = list(lod)
return lod
def lookup_lod(lod, **kw):
for row in lod:
for k,v in kw.iteritems():
if row[k] != str(v): break
else:
return row
return None
测试然后产生:
>>> lod = []
>>> populate_lod(lod, csv_fp)
>>>
>>> pprint(lookup_lod(lod, Row=1))
{'Name': 'Cat', 'Priority': '1', 'Row': '1', 'Year': '1998'}
>>> pprint(lookup_lod(lod, Name='Aardvark'))
{'Name': 'Aardvark', 'Priority': '1', 'Row': '4', 'Year': '2000'}
>>> pprint(query_lod(lod, sort_keys=('Priority', 'Year')))
[{'Name': 'Cat', 'Priority': '1', 'Row': '1', 'Year': '1998'},
{'Name': 'Dog', 'Priority': '1', 'Row': '3', 'Year': '1999'},
{'Name': 'Aardvark', 'Priority': '1', 'Row': '4', 'Year': '2000'},
{'Name': 'Wallaby', 'Priority': '1', 'Row': '5', 'Year': '2000'},
{'Name': 'Fish', 'Priority': '2', 'Row': '2', 'Year': '1998'},
{'Name': 'Zebra', 'Priority': '3', 'Row': '6', 'Year': '2001'}]
>>> pprint(query_lod(lod, sort_keys=('Year', 'Priority')))
[{'Name': 'Cat', 'Priority': '1', 'Row': '1', 'Year': '1998'},
{'Name': 'Fish', 'Priority': '2', 'Row': '2', 'Year': '1998'},
{'Name': 'Dog', 'Priority': '1', 'Row': '3', 'Year': '1999'},
{'Name': 'Aardvark', 'Priority': '1', 'Row': '4', 'Year': '2000'},
{'Name': 'Wallaby', 'Priority': '1', 'Row': '5', 'Year': '2000'},
{'Name': 'Zebra', 'Priority': '3', 'Row': '6', 'Year': '2001'}]
>>> print len(query_lod(lod, lambda r:1997 <= int(r['Year']) <= 2002))
6
>>> print len(query_lod(lod, lambda r:int(r['Year'])==1998 and int(r['Priority']) > 2))
0
我个人更喜欢SQLite版本,因为它更好地保留了你的类型(没有Python中的额外转换代码),并且很容易增长以适应未来的需求。但话说回来,我对SQL很满意,所以YMMV。
答案 1 :(得分:30)
我知道一个非常古老的问题,但是......
pandas DataFrame似乎是这里的理想选择。
http://pandas.pydata.org/pandas-docs/version/0.13.1/generated/pandas.DataFrame.html
来自blurb
二维大小可变,可能异构的表格数据 带有标记轴(行和列)的结构。算术运算 在行标签和列标签上对齐。可以被认为是一个像字典一样 Series对象的容器。主要的pandas数据结构
答案 2 :(得分:16)
我个人会使用行列表列表。由于每行的数据始终采用相同的顺序,因此只需访问每个列表中的元素,即可轻松按任意列进行排序。您还可以根据每个列表中的特定列轻松进行计数,并进行搜索。它基本上与二维阵列一样接近。
这里唯一的缺点是你必须知道数据的顺序,如果你改变了这个顺序,你将不得不改变你的搜索/排序例程来匹配。
你可以做的另一件事就是有一个词典列表。
rows = []
rows.append({"ID":"1", "name":"Cat", "year":"1998", "priority":"1"})
这将避免需要知道参数的顺序,因此您可以查看列表中的每个“年”字段。
答案 3 :(得分:6)
有一个Table类,其行是dict或更好的行对象的列表
在表中不直接添加行,但有一个更新少量查找映射的方法,例如为了名字 如果你没有按顺序添加行或者id不连续,你也可以拥有idMap e.g。
class Table(object):
def __init__(self):
self.rows = []# list of row objects, we assume if order of id
self.nameMap = {} # for faster direct lookup for row by name
def addRow(self, row):
self.rows.append(row)
self.nameMap[row['name']] = row
def getRow(self, name):
return self.nameMap[name]
table = Table()
table.addRow({'ID':1,'name':'a'})
答案 4 :(得分:5)
首先,鉴于您有一个复杂的数据检索方案,您确定甚至SQLite都是过度的吗?
你最终会有一个临时的,非正式指定的,错误缠身的,缓慢执行一半的SQLite,解释Greenspun's Tenth Rule。
那就是说,你说选择单个数据结构会影响搜索,排序或计数中的一个或多个是非常正确的,所以如果性能至关重要并且数据是恒定的,你可以考虑使用多个结构不同的目的。
最重要的是,衡量哪些操作会更常见,并决定哪种结构的成本会降低。
答案 5 :(得分:2)
因为它存在的最根本原因是作为在XML文件和SQL数据库之间来回发送数据的一种方式。
它是用西班牙语编写的(如果在编程语言中很重要),但它非常简单。
from BD_XML import Tabla
它定义了一个名为Tabla(Table)的对象,它可以用一个名称来创建,用于识别pep-246兼容数据库接口的预先创建的连接对象。
Table = Tabla('Animals')
然后你需要使用agregar_columna
(add_column)方法添加列,可以使用各种关键字参数:
campo
(字段):字段的名称
tipo
(type):存储的数据类型,如果您对导出到数据库不感兴趣,可以是'varchar'和'double'或python对象的名称后者。
defecto
(默认值):如果添加行时没有,则设置列的默认值
还有其他3个,但仅用于数据库铃声而不是实际功能
像:
Table.agregar_columna(campo='Name', tipo='str')
Table.agregar_columna(campo='Year', tipo='date')
#declaring it date, time, datetime or timestamp is important for being able to store it as a time object and not only as a number, But you can always put it as a int if you don't care for dates
Table.agregar_columna(campo='Priority', tipo='int')
然后使用+ =运算符添加行(如果要创建带有额外行的副本,则添加+)
Table += ('Cat', date(1998,1,1), 1)
Table += {'Year':date(1998,1,1), 'Priority':2, Name:'Fish'}
#…
#The condition for adding is that is a container accessible with either the column name or the position of the column in the table
然后,您可以生成XML并将其写入包含exportar_XML
(export_XML)和escribir_XML
(write_XML)的文件:
file = os.path.abspath(os.path.join(os.path.dirname(__file__), 'Animals.xml'))
Table.exportar_xml()
Table.escribir_xml(file)
然后使用importar_XML
(import_XML)将其与文件名一起导回,并指示您使用的是文件,而不是字符串文字:
Table.importar_xml(file, tipo='archivo')
#archivo means file
这是您以SQL方式使用Tabla对象的方法。
#UPDATE <Table> SET Name = CONCAT(Name,' ',Priority), Priority = NULL WHERE id = 2
for row in Table:
if row['id'] == 2:
row['Name'] += ' ' + row['Priority']
row['Priority'] = None
print(Table)
#DELETE FROM <Table> WHERE MOD(id,2) = 0 LIMIT 1
n = 0
nmax = 1
for row in Table:
if row['id'] % 2 == 0:
del Table[row]
n += 1
if n >= nmax: break
print(Table)
此示例假设名为“id”的列 但可以替换width row.pos作为例子。
if row.pos == 2: