Python/Flask - ValueError:关闭文件的I/O操作

Dav*_*vid 6 python pdf flask python-2.7

在有人说这是重复之前,我认为这不是因为我看过类似的问题而他们没有帮助我!

我在python中创建一个Flask服务器,我需要能够有一个显示pdf的url.

我试着使用以下代码:

@app.route('/pdf')
def pdfStuff():

        with open('pdffile.pdf', 'rb') as static_file:
                return send_file(static_file, attachment_filename='pdffile.pdf')
Run Code Online (Sandbox Code Playgroud)

这应该是这样的,当我去/pdf它时将显示pdf文件pdffile.pdf.

但是,这不起作用,因为当我运行代码时,我收到此错误:

ValueError: I/O operation on closed file
Run Code Online (Sandbox Code Playgroud)

情况怎么样?我的return语句在with语句中,因此不应该打开文件?

我试图用正常static_file = open(...)和使用try,并finally声明,如下所示:

static_file = open('pdffile.pdf','rb')
try:
        return send_file(static_file, attachment_filename='pdffile.pdf')
finally:
        static_file.close()
Run Code Online (Sandbox Code Playgroud)

上面的代码发生了同样的错误,我不明白为什么.有谁知道我做错了什么?

对不起,如果我是愚蠢的,有一些简单的我犯了错误!

非常感谢你提前!!

iur*_*vio 5

send_file与文件名一起使用,它将以您期望的方式打开、提供和关闭它。

@app.route('/pdf')
def pdfStuff():
    return send_file('pdffile.pdf')
Run Code Online (Sandbox Code Playgroud)


New*_*bie 5

尽管@iurisilvio 的答案解决了这个特定问题,但在任何其他情况下都不是有用的答案。我自己也在为此苦苦挣扎。

以下所有示例都抛出异常,ValueError: I/O operation on closed file.但为什么呢?

@app.route('/pdf')
def pdfStuff():
    with open('pdffile.pdf', 'rb') as static_file:
        return send_file(static_file, attachment_filename='pdffile.pdf')


@app.route('/pdf')
def pdfStuff():
    static_file = open('pdffile.pdf','rb')
    try:
        return send_file(static_file, attachment_filename='pdffile.pdf')
    finally:
        static_file.close()
Run Code Online (Sandbox Code Playgroud)

我正在做一些稍微不同的事情。像这样:

@page.route('/file', methods=['GET'])
def build_csv():

    # ... some query ...

    ENCODING = 'utf-8'
    bi = io.BytesIO()
    tw = io.TextIOWrapper(bi, encoding=ENCODING)
    c = csv.writer(tw)
    c.writerow(['col_1', 'col_2'])
    c.writerow(['1', '2'])

    bi.seek(0)
    return send_file(bi,
                     as_attachment=True,
                     attachment_filename='file.csv',
                     mimetype="Content-Type: text/html; charset={0}".format(ENCODING)
                     )
Run Code Online (Sandbox Code Playgroud)

对于前两种情况,答案很简单:

你给 一个流send_file,这个函数不会立即传输文件,而是包装流并将其返回给 Flask 以供将来处理。pdfStuff在 Flask 开始处理你的流之前,你的函数将准备好返回,并且在这两种情况下(with和),流将在你的函数返回之前finally关闭。

第三种情况更棘手(但这个答案为我指明了正确的方向:Why is TextIOWrapper close the给定的 BytesIO stream?)。以与上述相同的方式,bi仅在返回后进行处理build_csv。因此tw已经被抛弃给垃圾收集器了。当收集器销毁它时,tw将隐式关闭bi。这个问题的解决方案是tw.detach()在返回之前(这将停止TextIOWrapper影响流)。

旁注(如果我错了,请纠正我):这种行为是有限的,除非send_file提供类似文件的对象时,它会自行处理关闭。从文档( https://flask.palletsprojects.com/en/0.12.x/ ​​api/#flask.send_file)中尚不清楚是否处理了关闭。我认为是这样的(.close()源代码中存在一些+也已实现的send_file用途),在这种情况下,您的方法可以更正为:werkzeug.wsgi.FileWrapper.close()

@app.route('/pdf')
def pdfStuff():
    return send_file(open('pdffile.pdf','rb'), attachment_filename='pdffile.pdf')
Run Code Online (Sandbox Code Playgroud)

当然,在这种情况下,可以直接提供文件名。但在其他情况下,可能需要将文件流包装在某些操作管道中(解码/压缩)