ZetCode

Python sqlite3.Connection.row_factory

上次修改时间:2025 年 4 月 15 日

本综合指南探讨了 Python 的 sqlite3.Connection.row_factory 属性,该属性控制从 SQLite 查询返回行的方式。我们将涵盖基本用法、内置工厂、自定义工厂和实际示例。

基本定义

SQLite 连接的 row_factory 属性决定了结果行如何从游标操作中返回。默认情况下,行作为元组返回。

主要特点:它接受一个处理每一行的可调用对象,可以随时更改,并影响从连接创建的所有游标。该工厂接收游标和行元组作为参数。

默认行工厂(元组)

默认情况下,行作为元组返回。此示例演示了默认行为,而无需设置任何行工厂。

default_factory.py
import sqlite3

with sqlite3.connect(':memory:') as conn:
    conn.execute("CREATE TABLE test (id INTEGER, name TEXT)")
    conn.execute("INSERT INTO test VALUES (1, 'Alice')")
    
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM test")
    row = cursor.fetchone()
    
    print(type(row))  # <class 'tuple'>
    print(row[0], row[1])  # Access by index

此示例显示了默认行为,其中行作为元组返回。通过数字索引(第一列为 0)访问列值。

元组格式简单且内存效率高,但可读性不如命名访问,尤其是在列很多或架构更改时。

使用 sqlite3.Row 工厂

sqlite3.Row 工厂提供对列的命名访问和其他有用的功能。这是推荐的内置工厂。

row_factory_builtin.py
import sqlite3

with sqlite3.connect(':memory:') as conn:
    conn.row_factory = sqlite3.Row
    conn.execute("CREATE TABLE users (id INTEGER, name TEXT, age INTEGER)")
    conn.execute("INSERT INTO users VALUES (1, 'Bob', 35)")
    
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM users")
    row = cursor.fetchone()
    
    print(row['name'])  # Access by name
    print(row.keys())   # Column names
    print(row[1])      # Still accessible by index

此示例演示了使用 sqlite3.Row,它比元组提供了几个优点。可以通过名称或索引访问列。

其他功能包括:keys 方法获取列名、支持相等比较以及显示值的字符串表示形式。

自定义字典工厂

您可以创建一个自定义工厂,将行作为字典返回,以获得更大的灵活性。此示例显示了一个简单的字典工厂。

dict_factory.py
import sqlite3

def dict_factory(cursor, row):
    return {col[0]: row[idx] for idx, col in enumerate(cursor.description)}

with sqlite3.connect(':memory:') as conn:
    conn.row_factory = dict_factory
    conn.execute("CREATE TABLE products (id INTEGER, name TEXT, price REAL)")
    conn.execute("INSERT INTO products VALUES (1, 'Laptop', 999.99)")
    
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM products")
    row = cursor.fetchone()
    
    print(row['name'], row['price'])  # Access by name
    print(row)  # Full dictionary

此自定义工厂将每一行转换为一个字典,其中键是列名,值是行值。cursor.description 提供列元数据。

字典访问非常易读且灵活,但比元组占用更多的内存。在处理大量列时尤其有用。

命名元组工厂

为了在可读性和性能之间取得平衡,您可以使用命名元组。此示例使用 collections.namedtuple 创建一个工厂。

namedtuple_factory.py
import sqlite3
from collections import namedtuple

def namedtuple_factory(cursor, row):
    fields = [col[0] for col in cursor.description]
    Row = namedtuple('Row', fields)
    return Row(*row)

with sqlite3.connect(':memory:') as conn:
    conn.row_factory = namedtuple_factory
    conn.execute("CREATE TABLE employees (id INTEGER, name TEXT, department TEXT)")
    conn.execute("INSERT INTO employees VALUES (1, 'Carol', 'HR')")
    
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM employees")
    row = cursor.fetchone()
    
    print(row.name, row.department)  # Access by attribute
    print(row[0], row[1])           # Still accessible by index

此工厂为每个查询结果动态创建一个命名元组类。命名元组提供属性样式的访问,同时保持元组的性能。

优点是代码干净、可读,与字典相比,内存开销最小。命名元组类为每个查询创建一次。

类型转换工厂

行工厂还可以执行类型转换。此示例自动将特定列转换为 Python 类型。

type_conversion_factory.py
import sqlite3
from datetime import datetime

def convert_types(row):
    converted = []
    for value in row:
        if isinstance(value, str) and value.startswith('date:'):
            converted.append(datetime.strptime(value[5:], '%Y-%m-%d').date())
        else:
            converted.append(value)
    return converted

def type_aware_factory(cursor, row):
    row = convert_types(row)
    return {col[0]: row[idx] for idx, col in enumerate(cursor.description)}

with sqlite3.connect(':memory:') as conn:
    conn.row_factory = type_aware_factory
    conn.execute("CREATE TABLE events (id INTEGER, name TEXT, event_date TEXT)")
    conn.execute("INSERT INTO events VALUES (1, 'Meeting', 'date:2025-04-15')")
    
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM events")
    row = cursor.fetchone()
    
    print(type(row['event_date']))  # <class 'datetime.date'>
    print(row['event_date'].year)  # 2025

这个高级工厂检测特殊的字符串模式(如“date:YYYY-MM-DD”),并自动将它们转换为 Python 日期对象。

当您需要在应用程序中保持一致的类型处理或使用自定义 SQLite 类型适配器时,此类工厂非常有用。

不区分大小写的字典工厂

对于不区分大小写的列访问,您可以修改字典工厂。此示例显示了一个将列名规范化为小写的工厂。

case_insensitive_factory.py
import sqlite3

def case_insensitive_factory(cursor, row):
    return {col[0].lower(): row[idx] for idx, col in enumerate(cursor.description)}

with sqlite3.connect(':memory:') as conn:
    conn.row_factory = case_insensitive_factory
    conn.execute("CREATE TABLE books (ID INTEGER, Title TEXT, Author TEXT)")
    conn.execute("INSERT INTO books VALUES (1, 'Python Guide', 'J. Smith')")
    
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM books")
    row = cursor.fetchone()
    
    # All these work regardless of original case
    print(row['id'], row['title'], row['author'])
    print(row['ID'], row['Title'], row['Author'])
    print(row['Id'], row['tiTle'], row['AUTHOR'])

此工厂将返回的字典中的所有列名规范化为小写,从而使列访问不区分大小写,同时保留原始值。

当您处理具有不一致列命名约定的数据库或想要标准化访问模式时,这尤其有用。

自定义类工厂

为了获得最大的控制权,您可以返回自定义类实例。此示例创建了一个工厂,该工厂返回具有属性和字典访问权限的对象。

class_factory.py
import sqlite3

class Record:
    def __init__(self, data):
        self._data = data
    
    def __getattr__(self, name):
        try:
            return self._data[name]
        except KeyError:
            raise AttributeError(f"No such attribute: {name}")
    
    def __getitem__(self, key):
        return self._data[key]
    
    def __repr__(self):
        return f"Record({self._data})"

def class_factory(cursor, row):
    data = {col[0]: row[idx] for idx, col in enumerate(cursor.description)}
    return Record(data)

with sqlite3.connect(':memory:') as conn:
    conn.row_factory = class_factory
    conn.execute("CREATE TABLE inventory (item_id INTEGER, item_name TEXT, quantity INTEGER)")
    conn.execute("INSERT INTO inventory VALUES (101, 'Keyboard', 15)")
    
    cursor = conn.cursor()
    cursor.execute("SELECT * FROM inventory")
    row = cursor.fetchone()
    
    print(row.item_name)    # Attribute access
    print(row['quantity'])  # Dictionary access
    print(row)              # Custom representation

此工厂返回自定义 Record 类的实例,该类支持对列值进行属性样式和字典样式访问。

自定义类工厂提供了最大的灵活性,允许您向结果对象添加方法、属性和其他行为。

最佳实践

资料来源

作者

我的名字是 Jan Bodnar,我是一位充满热情的程序员,拥有丰富的编程经验。自 2007 年以来,我一直在撰写编程文章。到目前为止,我已经撰写了 1,400 多篇文章和 8 本电子书。我拥有超过十年的编程教学经验。

列出所有 Python 教程