【问题标题】:Context manager for Python's MySQLdbPython 的 MySQLdb 的上下文管理器
【发布时间】:2011-12-25 10:40:27
【问题描述】:

我习惯(被宠坏了?)python 的SQLite 接口来处理SQL 数据库。 python 的 SQLite API 中的一个不错的功能是“上下文管理器”,即 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,但它没有提供完整的解决方案。

【问题讨论】:

    标签: python mysql with-statement contextmanager


    【解决方案1】:

    Previously,MySQLdb 连接是上下文管理器。 然而,从this commit on 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 看起来很有希望。谢谢。
    • @unutbu 不应该在 enter 函数中初始化连接吗?我认为它更安全。这是一个例子geeksforgeeks.org/context-manager-in-python
    • @callmeGuy:__exit__ 函数假定 self.connection 存在。如果在__enter__ 函数中实例化连接并引发异常,那么__exit__ 函数将引发第二个异常,因为没有connection 可以调用rollbackcommit。所以不,它不会更安全。
    • @callmeGuy:可以在github source code 中找到进一步的佐证。您可以看到开发人员(更改之前)没有在__enter__ 中初始化连接。另请参阅doc example
    • @callmeGuy:不过,您的问题确实促使我重写了代码。连接和游标不是同一个东西,所以我将两者分开,所以每个都是它自己的上下文管理器。我也改用 contextlib.contextmanager 装饰器,而不是显式定义 __enter____exit__,因为它使代码更短且更具可读性。
    【解决方案2】:

    认为自从最初提出这个问题以来事情已经发生了变化。有点令人困惑(至少从我的角度来看),对于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')
    

    【讨论】:

    • 自动提交?如自动提交模式,所以你没有事务?
    最近更新 更多