当我将光标传递给StreamingHttpResponse时,为什么我的光标在生成器函数中关闭?

时间:2018-05-12 16:15:42

标签: python django psycopg2

我有一个可能很大的查询集,我不想加载到内存中。它是一个自定义的SQL语句。

django.http.StreamingHttpResponse采用iterator(生成器)参数。为了避免将所有内容加载到内存中,我使用了Postgres服务器端游标和fetchmany(虽然我还没有验证这确实有效)。

这是我通过的发电机功能:

def queryset_generator(cursor, chunk_size=CHUNK_SIZE):
    while True:
        if cursor.closed:
            yield "cursor closed!"
            break
        rows = cursor.fetchmany(chunk_size)
        if not rows:
            break
        yield rows

我测试光标是否关闭,否则当后面的代码尝试访问一个关闭的光标时,psycopg2会抱怨。

以下是我在视图中传递它的方法(简化SQL):

with connections['mydb'].cursor() as cursor:
    cursor.execute("SELECT * FROM foobar;")
    return StreamingHttpResponse(queryset_generator(cursor))

这始终如一地给了我

  

光标关闭!

为什么光标在我的生成器功能中关闭?如果我在我的视图中这样做,它可以正常工作:

with connections['mydb'].cursor() as cursor:
    cursor.execute("SELECT * FROM foobar;")
    return StreamingHttpResponse(cursor.fetchall())

也可能值得注意的是,这在shell中运行良好:

cursor = connections['mydb'].cursor()
cursor.execute(...)
for x in StreamingHttpResponse(queryset_generator(cursor))._iterator:
    print(x)

1 个答案:

答案 0 :(得分:3)

  

为什么光标在我的生成器功能中关闭?

因为您使用return退出了上下文管理器:

return StreamingHttpResponse(queryset_generator(cursor))

这将退出with块,在上下文管理器上触发__exit__方法,此方法将关闭游标。 with语句或上下文管理器无法知道您刚刚将对cursor对象的引用传递给仍需要它保持打开的其他对象。 with并不关心引用,只关心语义块的结束。

如果您需要保持光标打开,直到StreamingHttpResponse()实例完成流数据,您就无法在return语句周围使用上下文管理器。

在上下文管理器中传入光标而不使用它,并使queryset_generator()函数负责使用with

def queryset_generator(cursor, chunk_size=CHUNK_SIZE):
    with cursor:
        while True:
            if cursor.closed:
                yield "cursor closed!"
                break
            rows = cursor.fetchmany(chunk_size)
            if not rows:
                break
            yield rows

cursor = connections['mydb'].cursor()
cursor.execute("SELECT * FROM foobar;")
return StreamingHttpResponse(queryset_generator(cursor))

现在光标一直保持打开状态,直到while中的queryset_generator()循环完成。