杰瑞科技汇

Python cursorclass 是什么?如何使用?

Of course! Let's dive deep into cursorclass in Python, specifically in the context of database libraries like SQLite and MySQL Connector/Python.

Python cursorclass 是什么?如何使用?-图1
(图片来源网络,侵删)

What is a cursorclass?

At its core, a cursorclass is a custom class that you provide to a database connection object. When you create a cursor using connection.cursor(), instead of getting the default cursor object, you get an instance of your custom class.

The primary purpose is to extend or modify the behavior of the standard database cursor. This allows you to add extra functionality, change default settings, or implement custom logic without having to repeat code every time you interact with the database.


The Standard Cursor (The Baseline)

First, let's understand what a standard cursor does. When you connect to a database, you use a cursor to execute SQL commands and fetch results.

Example with SQLite (Standard Way):

Python cursorclass 是什么?如何使用?-图2
(图片来源网络,侵删)
import sqlite3
# 1. Connect to a database (or create it)
conn = sqlite3.connect('example.db')
# 2. Get a standard cursor
cursor = conn.cursor()
# 3. Execute a command
cursor.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)")
# 4. Execute a query
cursor.execute("SELECT * FROM users")
rows = cursor.fetchall()
# 5. Close the cursor and connection
cursor.close()
conn.close()
print(rows) # Output: []

This works perfectly, but it has some limitations:

  • Repetitive Code: You have to write cursor.execute() and cursor.fetchall() for every query.
  • No Context Management: You have to remember to manually close the cursor (cursor.close()), which can lead to resource leaks if forgotten.
  • No Custom Logic: If you want all your queries to be logged, or to automatically convert results into a specific format (like dictionaries), you have to add that logic to every single query.

Why Use a Custom cursorclass?

A custom cursor solves these problems by centralizing behavior.

Key Use Cases:

  1. Automatic Logging: Log every SQL query that is executed.
  2. Asynchronous Operations: Create an async cursor for use with asyncio.
  3. Result Transformation: Automatically fetch rows as dictionaries instead of tuples, which is much more readable.
  4. Automatic Resource Management: Implement __enter__ and __exit__ to use the cursor with a with statement, ensuring it's always closed.
  5. Adding Helper Methods: Create methods like execute_and_fetch_one() or insert_and_return_id() to simplify common tasks.

Example 1: The Most Common Use Case - Dictionary Cursor

The most popular reason to use a custom cursor is to get results as dictionaries instead of tuples. A tuple is indexed (row[0]), while a dictionary is keyed (row['column_name']), making your code self-documenting and less prone to errors if you add/remove columns.

Python cursorclass 是什么?如何使用?-图3
(图片来源网络,侵删)

Let's create a DictCursor for SQLite.

Step 1: Define the Custom Cursor Class

The key is to inherit from the library's base cursor class (e.g., sqlite3.Cursor) and override the fetch... methods.

import sqlite3
class DictCursor(sqlite3.Cursor):
    """
    A custom cursor that fetches rows as dictionaries.
    """
    def fetchone(self):
        """
        Fetches the next row and returns it as a dictionary.
        """
        row = super().fetchone()
        if row is None:
            return None
        # The description attribute contains column names
        columns = [col[0] for col in self.description]
        return dict(zip(columns, row))
    def fetchall(self):
        """
        Fetches all remaining rows and returns them as a list of dictionaries.
        """
        rows = super().fetchall()
        if not rows:
            return []
        columns = [col[0] for col in self.description]
        return [dict(zip(columns, row)) for row in rows]
    # You can also implement fetchmany if needed
    def fetchmany(self, size=None):
        if size is None:
            size = self.arraysize
        rows = super().fetchmany(size)
        if not rows:
            return []
        columns = [col[0] for col in self.description]
        return [dict(zip(columns, row)) for row in rows]

Step 2: Use the Custom Cursor with the Connection

You pass the class itself (not an instance) to the cursor_factory parameter when connecting.

import sqlite3
from pprint import pprint # For pretty printing
# Assume the DictCursor class from above is defined here
# Connect and specify our custom cursor class
conn = sqlite3.connect('example.db', cursor_factory=DictCursor)
# No need to pass the class to conn.cursor() anymore!
# It will automatically use our DictCursor
cursor = conn.cursor()
# Insert some data to have something to query
cursor.execute("INSERT INTO users (name, email) VALUES (?, ?)", ('Alice', 'alice@example.com'))
cursor.execute("INSERT INTO users (name, email) VALUES (?, ?)", ('Bob', 'bob@example.com'))
conn.commit()
# Now, fetch the results
cursor.execute("SELECT * FROM users")
users = cursor.fetchall()
# pprint will show the dictionary structure nicely
pprint(users)
# Output:
# [{'email': 'alice@example.com', 'id': 1, 'name': 'Alice'},
#  {'email': 'bob@example.com', 'id': 2, 'name': 'Bob'}]
# You can access columns by name, which is much clearer!
print(f"First user's name: {users[0]['name']}")
# Clean up
cursor.close()
conn.close()

Example 2: A Cursor with Context Management and Logging

This example shows how to create a more advanced cursor that handles its own lifecycle and logs every query.

import sqlite3
import logging
import time
# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(message)s')
class SmartCursor(sqlite3.Cursor):
    """
    A custom cursor that logs queries and can be used in a 'with' statement.
    """
    def __enter__(self):
        """Enter the runtime context related to this object."""
        return self
    def __exit__(self, exc_type, exc_val, exc_tb):
        """Exit the runtime context related to this object."""
        # The cursor is closed automatically when exiting the 'with' block
        self.close()
    def execute(self, sql, parameters=None):
        """Override execute to add logging and timing."""
        start_time = time.time()
        logging.info(f"Executing query: {sql} with params: {parameters}")
        try:
            if parameters:
                result = super().execute(sql, parameters)
            else:
                result = super().execute(sql)
            duration = time.time() - start_time
            logging.info(f"Query successful. Duration: {duration:.4f} seconds. Rows affected: {self.rowcount}")
            return result
        except Exception as e:
            logging.error(f"Query failed: {e}")
            raise # Re-raise the exception to be handled by the caller
# --- Usage ---
conn = sqlite3.connect(':memory:') # Use an in-memory DB for this example
conn.row_factory = sqlite3.Row # Optional: for easy column access if needed
cursor = conn.cursor()
# Create the table
cursor.execute("CREATE TABLE products (id INTEGER, name TEXT)")
# Use the smart cursor with a 'with' statement
with conn:
    # The connection itself can be used as a context manager for transactions
    with SmartCursor(conn) as c:
        c.execute("INSERT INTO products (id, name) VALUES (?, ?)", (101, 'Laptop'))
        c.execute("INSERT INTO products (id, name) VALUES (?, ?)", (102, 'Mouse'))
        # This query will be logged
        c.execute("SELECT * FROM products WHERE id = ?", (101,))
        product = c.fetchone()
        print(f"Fetched product: {product}") # product is a sqlite3.Row object, which is dict-like
# The 'with' block ensures 'c.close()' is called automatically.
# The 'with conn:' block ensures the transaction is committed.
conn.close()

cursorclass in Other Libraries (e.g., MySQL)

The concept is identical in other database libraries. For example, with mysql-connector-python, you can create a custom cursor and pass it to connection.cursor().

import mysql.connector
from mysql.connector.abstracts import MySQLCursorAbstract
class MyDictCursor(MySQLCursorAbstract):
    # ... implementation would be similar to the SQLite example ...
    # You would override fetchone, fetchall, etc., to return dicts.
    pass
# --- Usage ---
config = {
    'user': 'your_user',
    'password': 'your_password',
    'host': '127.0.0.1',
    'database': 'your_db'
}
try:
    cnx = mysql.connector.connect(**config)
    # Pass the CLASS to the cursor_factory parameter
    with cnx.cursor(cursor_class=MyDictCursor) as cursor:
        cursor.execute("SELECT * FROM your_table")
        for row in cursor:
            print(row['column_name']) # Access by column name
finally:
    if cnx.is_connected():
        cnx.close()

Summary: When to Use cursorclass

When to Use cursorclass When NOT to Use cursorclass
✅ You need to centralize common logic (logging, transformations, helper methods). ❌ For a one-off, simple script where the overhead isn't justified.
✅ You want to change the fundamental behavior of a cursor (e.g., async, dict results). ❌ If you only need to change the connection's behavior (use connection attributes instead).
✅ You want to enforce consistent patterns across your entire application's data access. ❌ If the library already provides a built-in cursor that does what you need (e.g., sqlite3.Row).
✅ You need to manage resources automatically (context managers).
分享:
扫描分享到社交APP
上一篇
下一篇