Python的MySQLdb上下文管理器

18

我习惯于(被宠坏了?)python的SQLite接口来处理SQL数据库。 Python SQLite的一个不错特性是“上下文管理器”,即Python的with语句。 我通常以以下方式执行查询:

import as sqlite

with sqlite.connect(db_filename) as conn:
    query = "INSERT OR IGNORE INTO shapes VALUES (?,?);"
    results = conn.execute(query, ("ID1","triangle"))

使用上面的代码,如果我的查询修改了数据库并且我忘记运行conn.commit(),上下文管理器会在退出with语句时自动运行它。 它还可以很好地处理异常:如果在提交任何内容之前发生异常,则会回滚数据库。

我现在正在使用MySQLdb接口,但似乎没有类似的上下文管理器。如何创建自己的上下文管理器?有一个相关的问题here,但它并没有提供完整的解决方案。

2个回答

28

之前,MySQLdb的连接是上下文管理器。 但是自2018-12-04这个提交以来,MySQLdb的连接不再是上下文管理器, 用户必须显式地调用conn.commit()或conn.rollback(),或编写自己的上下文管理器,例如下面的代码。


你可以像这样使用:

import config
import MySQLdb
import MySQLdb.cursors as mc
import _mysql_exceptions
import contextlib
DictCursor = mc.DictCursor
SSCursor = mc.SSCursor
SSDictCursor = mc.SSDictCursor
Cursor = mc.Cursor

@contextlib.contextmanager
def connection(cursorclass=Cursor,
               host=config.HOST, user=config.USER,
               passwd=config.PASS, dbname=config.MYDB,
               driver=MySQLdb):
    connection = driver.connect(
            host=host, user=user, passwd=passwd, db=dbname,
            cursorclass=cursorclass)
    try:
        yield connection
    except Exception:
        connection.rollback()
        raise
    else:
        connection.commit()
    finally:
        connection.close()

@contextlib.contextmanager
def cursor(cursorclass=Cursor, host=config.HOST, user=config.USER,
           passwd=config.PASS, dbname=config.MYDB):
    with connection(cursorclass, host, user, passwd, dbname) as conn:
        cursor = conn.cursor()
        try:
            yield cursor
        finally:
            cursor.close()


with cursor(SSDictCursor) as cur:
    print(cur)
    connection = cur.connection
    print(connection)
    sql = 'select * from table'
    cur.execute(sql)
    for row in cur:
        print(row)
要使用它,您需要将 config.py 放置在 PYTHONPATH 中,并在那里定义 HOST、USER、PASS 和 MYDB 变量。

非常棒的解决方案!你不仅提供了MySQLdb的答案,还可以与其他驱动程序一起使用。此外,oursql看起来很有前途。谢谢。 - conradlee
@unutbu 难道连接不应该在 enter 函数中初始化吗?我认为这样更安全。这里有一个例子 https://www.geeksforgeeks.org/context-manager-in-python/ - callmeGuy
@callmeGuy:__exit__函数假设self.connection存在。如果在__enter__函数中实例化连接并引发异常,则__exit__函数将引发第二个异常,因为没有connection可以调用 rollbackcommit。因此,不,这样做并不更安全。 - unutbu
@callmeGuy:进一步的证实可以在github源代码中找到。您可以看到开发人员(在更改之前)没有在__enter__中初始化连接。还请参阅此文档示例 - unutbu
@callmeGuy:您的问题确实促使我重写了代码。连接和游标不是相同的东西,因此我将它们分开,使每个都成为自己的上下文管理器。我还改用contextlib.contextmanager装饰器,而不是显式定义__enter____exit__,因为它使代码更短,更易读。 - unutbu
@unutbu,我希望你把你的代码保留为一个类,对我来说至少更有用。无论如何,你提出了一些有趣的观点。 - callmeGuy

19

自这个问题最初被提出以来,情况已经发生了变化。有点令人困惑的是(至少从我的角度来看),对于MySQLdb的最新版本,如果您在上下文中使用连接,则会获得一个游标(与oursql示例一样),而不是会自动关闭的东西(例如您打开的文件)。

以下是我所做的:

from contextlib import closing
with closing(getConnection()) as conn: #ensure that the connection is closed
    with conn as cursor:               #cursor will now auto-commit
        cursor.execute('SELECT * FROM tablename')

2
自动提交?也就是说,自动提交模式,所以你没有事务? - Halfgaar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接