16

我正在使用 sqlite3 的 python api,我有一个用于存储语言的小表,其中包含 id、name 和 creation_date 字段。我正在尝试namedtuple按照文档的建议将原始查询结果映射到 a 中,这样我就可以以更易读的方式管理行,所以这是我的namedtuple.

LanguageRecord = namedtuple('LanguageRecord', 'id, name, creation_date')

文档为映射建议的代码如下:

for language in map(LanguageRecord._make, c.fetchall()):
  # do something with languages

当我想返回一组语言时这很好,但在这种情况下我只想检索一种语言:

c.execute('SELECT * FROM language WHERE name=?', (name,))

所以我的第一次尝试是这样的:

language = map(LanguageRecord._make, c.fetchone())

此代码不起作用,因为fetchone()返回一个元组而不是一个包含一个元组的列表,因此该map函数尝试namedtuples为每个元组字段创建三个一个。

我解决这个问题的第一种方法是显式创建一个列表并将元组结果附加到它上面,例如:

languages = []
languages.append(c.fetchone())
for language in map(LanguageRecord._make, languages):
  # do something with language

我的第二种方法是使用fetchall()虽然我只想要一个记录。我可以在数据库中设置带有unique约束的名称字段,以便只保证一个结果。

for language in map(LanguageRecord._make, c.fetchall()):
  # do something with languages

可以使用另一种方法,而fetchall()[0]不会unique限制只保证一个结果。

我的问题是处理这个问题的最好和最常用的方法,我应该总是使用fetchall来维护一个通用接口并让数据库管理唯一性逻辑吗?还是我应该像方法1一样明确创建一个列表?有没有更简单的方法来完成这项任务?

4

5 回答 5

35

有一个更简单的方法!Sqlite3 为用户提供了一种定义“行工厂”的方法。这些行工厂采用游标和元组行,可以返回它想要的任何类型的对象。

一旦你设置了行工厂

con.row_factory = my_row_factory

那么游标返回的行将是my_row_factory应用于元组行的结果。例如,

import sqlite3
import collections

LanguageRecord = collections.namedtuple('LanguageRecord', 'id name creation_date')
def namedtuple_factory(cursor, row):
    return LanguageRecord(*row)

con = sqlite3.connect(":memory:")
con.row_factory = namedtuple_factory
cur = con.cursor()
cur.execute("select 1,2,3")
print(cur.fetchone())

产量

LanguageRecord(id=1, name=2, creation_date=3)

有关如何定义命名元组工厂的另一个示例,请参阅这篇文章


顺便说一句,如果你设置

conn.row_factory = sqlite3.Row

然后行作为字典返回,其键是表的列名。因此,与其使用类似的东西访问 namedtuple 的一部分,不如row.creation_date使用内置的sqlite3.Row行工厂并使用row['creation_date'].

于 2013-05-02T10:49:30.680 回答
8

一个改进row_factory实际上是这样,它可以被重用于各种查询:

from collections import namedtuple

def namedtuple_factory(cursor, row):
    """Returns sqlite rows as named tuples."""
    fields = [col[0] for col in cursor.description]
    Row = namedtuple("Row", fields)
    return Row(*row)

conn = sqlite3.connect(":memory:")
conn.row_factory = namedtuple_factory
cur = con.cursor()
于 2018-01-20T17:24:03.807 回答
0

row_factory上面还有一个namedtuple

from collection import namedtuple

def namedtuple_factory(cursor, row, cls=[None]):
    rf = cls[0]
    if rf is None:
        fields = [col[0] for col in cursor.description]
        cls[0] = namedtuple("Row", fields)
        return cls[0](*row)
    return rf(*row)

为了使用其他类工厂,可以进一步概括:

def make_row_factory(cls_factory, **kw):
    def row_factory(cursor, row, cls=[None]):
        rf = cls[0]
        if rf is None:
            fields = [col[0] for col in cursor.description]
            cls[0] = cls_factory("Row", fields, **kw)
            return cls[0](*row)
        return rf(*row)
    return row_factory

这些工厂函数对于所有查询结果都具有相同字段的情况很有用。

例子:

  1. namedtuple_factory = make_row_factory(namedtuple)

  2. import dataclass

    row_factory = make_row_factory(dataclass.make_dataclass)

  3. pip3 install recordclass

    import recordclass

    row_factory = make_row_factory(recordclass.make_dataclass, fast_new=True)

以下是一些用于比较不同方式的性能计数器(debian linux、64 位、python 3.9)。

创建测试数据库的脚本:

N = 1000000
conn = sqlite3.connect('example.db')
c = conn.cursor()
c.execute('''CREATE TABLE test
             (id int, x real, y real, p int, q int)''')
gen = ((i, random(), random(), randint(0,N), randint(0,N)) for i in range(N))
c.executemany("INSERT INTO test VALUES (?,?,?,?,?)", gen)
conn.commit()
conn.close()

默认:

conn = sqlite3.connect('example.db')
c = conn.cursor()
%time res = [row for row in c.execute("SELECT id,x,y,p,q FROM test")]
conn.close()
print(N * sys.getsizeof(res[0]) // 1000000, 'Mb')

CPU times: user 971 ms, sys: 92.1 ms, total: 1.06 s
Wall time: 1.06 s
80 Mb

sqlite3.行:

conn = sqlite3.connect('example.db')
conn.row_factory = sqlite3.Row
c = conn.cursor()
%time res = [row for row in c.execute("SELECT id,x,y,p,q FROM test")]
conn.close()
# print(N * sys.getsizeof(res[0]) // 1000000, 'Mb')

CPU times: user 1.11 s, sys: 80.1 ms, total: 1.19 s
Wall time: 1.19 s

命名元组:

from collections import namedtuple Row = namedtuple("Row", "id xyp q") conn = sqlite3.connect('example.db') c = conn.cursor() %time res = [Row(*row) for row in c.execute("SELECT id,x,y,p,q FROM test")] conn.close() print(N * sys.getsizeof(res[0]) // 1000000, 'Mb')

CPU times: user 1.89 s, sys: 71.8 ms, total: 1.96 s
Wall time: 1.96 s
80 Mb

基于命名元组的行工厂:

conn = sqlite3.connect('example.db')
conn.row_factory = make_row_factory(namedtuple)
c = conn.cursor()
%time res = [row for row in c.execute("SELECT id,x,y,p,q FROM test")]
conn.close()
print(N * sys.getsizeof(res[0]) // 1000000, 'Mb')

CPU times: user 1.93 s, sys: 116 ms, total: 2.05 s
Wall time: 2.05 s
80 Mb

记录类:

from recordclass import make_dataclass
Row = make_dataclass("Row", "id x y p q", fast_new=True)
conn = sqlite3.connect('example.db')
c = conn.cursor()
%time res = [Row(*row) for row in c.execute("SELECT id,x,y,p,q FROM test")]
conn.close()
print(N * sys.getsizeof(res[0]) // 1000000, 'Mb')

CPU times: user 1 s, sys: 72.2 ms, total: 1.08 s
Wall time: 1.07 s
56 Mb

基于记录类的行工厂:

conn = sqlite3.connect('example.db')
conn.row_factory = make_row_factory(make_dataclass, fast_new=True)
c = conn.cursor()
%time res = [row for row in c.execute("SELECT id,x,y,p,q FROM test")]
conn.close()
print(N * sys.getsizeof(res[0]) // 1000000, 'Mb')

CPU times: user 1.11 s, sys: 76.2 ms, total: 1.19 s
Wall time: 1.19 s
56 Mb
于 2021-08-05T06:12:12.693 回答
0

我演示了如何从 sql 查询中获取结果数据帧并将其转换为命名元组列表。我没有将数据框列动态绑定到名称元组名称,不确定这是否可能。

LanguageRecord=namedtuple('Generic',['id','name','creation_date'])
def map_to_language_record(row):
    return LanguageRecord(row.id, row.name, row.creation_date)

df=pd.DataFrame({'id':[1,2,3],'name':['bob','dick','jane'],'creation_date': 
  ['1/1/2021','1/2/2021','1/3/2021']})

languages = list(map(map_to_language_record, df.itertuples()))
print(languages)

输出:

[Generic(id=1, name='bob', creation_date='1/1/2021'), Generic(id=2, name='dick', creation_date='1/2/2021'), Generic(id=3, name='jane', creation_date='1/3/2021')]
于 2021-10-01T15:19:14.737 回答
-2

我认为更好用, for language in map(LanguageRecord._make, c.fetchall()[:1]): 因为它可能会导致 fetchall()[0] 导致 IndexError。

如果您需要一个结果并且查询中已经存在“WHERE”。据我了解,查询应该返回一行。早期优化是邪恶的。:)

于 2013-05-02T10:50:38.913 回答