为什么MySQLdb Connection上下文管理器不关闭游标?

jmi*_*loy 19 python mysql mysql-python contextmanager

MySQLdb Connections有一个基本的上下文管理器,可以在enter上创建一个游标,在退出时回退或提交,并且隐式不会抑制异常.来自连接源:

def __enter__(self):
    if self.get_autocommit():
        self.query("BEGIN")
    return self.cursor()

def __exit__(self, exc, value, tb):
    if exc:
        self.rollback()
    else:
        self.commit()
Run Code Online (Sandbox Code Playgroud)

那么,有没有人知道为什么光标在退出时没有关闭?


起初,我认为这是因为关闭游标没有做任何事情,并且游标只有一个关闭方法来参考Python DB API(参见本答案的评论).但是,事实是关闭光标会烧掉剩余的结果集(如果有),并禁用光标.从光标源:

def close(self):
    """Close the cursor. No further queries will be possible."""
    if not self.connection: return
    while self.nextset(): pass
    self.connection = None
Run Code Online (Sandbox Code Playgroud)

在退出处关闭光标会很容易,所以我不得不假设它没有故意完成.另一方面,我们可以看到,当一个游标被删除时,无论如何它都会被关闭,所以我猜垃圾收集器最终会绕过它.我对Python中的垃圾收集知之甚少.

def __del__(self):
    self.close()
    self.errorhandler = None
    self._result = None
Run Code Online (Sandbox Code Playgroud)

另一个猜测是,您可能希望在with块之后重新使用光标.但我想不出你为什么需要这样做的任何理由.难道你不能总是在其上下文中使用游标,并且只为下一个事务使用单独的上下文吗?

要非常清楚,这个例子显然没有意义:

with conn as cursor:
    cursor.execute(select_stmt)

rows = cursor.fetchall()
Run Code Online (Sandbox Code Playgroud)

它应该是:

with conn as cursor:
    cursor.execute(select_stmt)
    rows = cursor.fetchall()
Run Code Online (Sandbox Code Playgroud)

这个例子也没有意义:

# first transaction
with conn as cursor:
    cursor.execute(update_stmt_1)

# second transaction, reusing cursor
try:
    cursor.execute(update_stmt_2)
except:
    conn.rollback()
else:
    conn.commit()
Run Code Online (Sandbox Code Playgroud)

它应该只是:

# first transaction
with conn as cursor:
    cursor.execute(update_stmt_1)

# second transaction, new cursor
with conn as cursor:
    cursor.execute(update_stmt_2)
Run Code Online (Sandbox Code Playgroud)

再次,关闭退出光标会有什么危害,有什么好处不关闭呢?

J R*_*ape 10

直接回答你的问题:在一个with区块结束时我无法看到任何伤害.我不能说为什么在这种情况下没有这样做.但是,由于在这个问题上缺乏活动,我对代码历史进行了搜索,并会对可能无法调用的原因提出一些想法(猜测):close()

  1. 旋转调用nextset()可能会引发异常的可能性很小- 可能已经观察到并且被视为不合需要.这可能是为什么较新版本cursors.py包含此结构的原因close():

    def close(self):
        """Close the cursor. No further queries will be possible."""
        if not self.connection:
            return
    
        self._flush()
        try:
            while self.nextset():
                pass
        except:
            pass
        self.connection = None
    
    Run Code Online (Sandbox Code Playgroud)
  2. 有一些(有点遥远的)潜力可能需要一些时间才能完成所有剩余的结果.因此close()可能无法调用以避免进行一些不必要的迭代.我认为,你是否认为值得保存这些时钟周期是主观的,但你可以按照"如果没有必要,不要这样做"的方式进行争论.

  3. 浏览sourceforge提交,该功能在2007年通过此提交添加到主干,看起来这部分connections.py从那以后没有改变.这是基于此提交的合并,它具有消息

    http://docs.python.org/whatsnew/pep-343.html中 所述,为with语句添加Python-2.5支持请测试

    你引用的代码从那以后就没有改变过.

    这促使我最后的想法 - 它可能只是一个刚刚起作用的第一次尝试/原型,因此从未改变过.


更现代的版本

您链接到旧版连接器的源.我注意到有同一库更积极的叉子在这里,这是我在第1点链接到我的关于"新版本"的意见.

请注意,该模块的最新版本已经实施__enter__()__exit__()内部cursor本身:在这里看到. __exit__()这里做了 调用self.close(),也许这提供了一种更标准的方式来使用with语法,例如

with conn.cursor() as c:
    #Do your thing with the cursor
Run Code Online (Sandbox Code Playgroud)

结束说明

NB我想我应该添加,据我所知,垃圾收集(不是专家)一旦没有引用conn,它将被解除分配.此时将不会引用游标对象,它也将被解除分配.

但是,调用cursor.close()并不意味着它将被垃圾收集.它只是烧掉结果并设置连接None.这意味着它无法重复使用,但不会立即进行垃圾回收.您可以通过cursor.close()with阻止之后手动调用然后打印某些属性来说服自己cursor


NB 2我认为这是一种有点不同寻常的with语法用法,conn因为它已经存在于外部范围内 - 不像说,更常见with open('filename') as f:的是在with块结束后没有任何对象挂在引用上.