首页 > 解决方案 > 为什么当我将光标传递给 StreamingHttpResponse 时,我的生成器函数中的光标已关闭?

问题描述

我有一个可能很大的查询集,我不想加载到内存中。这是一个自定义 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)

标签: pythondjangopsycopg2

解决方案


为什么我的生成器函数中的光标关闭了?

因为您使用以下命令退出上下文管理器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()完成。


推荐阅读