Python FTP“块”迭代器(无需将整个文件加载到内存中)

4

在stackoverflow上有很多关于检索FTP文件并将其写入流(如字符串缓冲区或文件)的答案,然后可以对其进行迭代。

例如:从FTP python中读取缓冲区中的文件

但是,这些解决方案涉及将整个文件加载到内存中或在开始处理内容之前将其下载到磁盘中。

我没有足够的内存来缓冲整个文件,也无法访问磁盘。这可以通过在回调函数中处理数据来完成,但我想知道是否可能将FTP代码包装在一些神奇的东西中,以返回迭代器而不是在我的代码中添加回调。

即:而不是:

def get_ftp_data(handle_chunk):
    ...
    ftp.login('uesr', 'password') # authentication required
    ftp.retrbinary('RETR etc', handle_chunk)
    ...

get_ftp_data(do_stuff_to_chunk)

我想要:

for chunk in get_ftp_data():
    do_stuff_to_chunk(chunk)

与现有答案不同的是,我希望在迭代之前不需要将整个ftp文件写入磁盘或内存中。


1
这里有一个类似的问题:如何将带回调函数的函数转换为Python生成器? - Jan Vlcinsky
1个回答

7

你需要将 retrbinary 调用放在另一个线程中,并让回调函数将数据块提供给迭代器:

import threading, Queue

def ftp_chunk_iterator(FTP, command):
    # Set maxsize to limit the number of chunks kept in memory at once.
    queue = Queue.Queue(maxsize=some_appropriate_size)

    def ftp_thread_target():
        FTP.retrbinary(command, callback=queue.put)
        queue.put(None)

    ftp_thread = threading.Thread(target=ftp_thread_target)
    ftp_thread.start()

    while True:
        chunk = queue.get()
        if chunk is not None:
            yield chunk
        else:
            return

如果您无法使用线程,那么最好的方法是将回调函数编写为协程:
from contextlib import closing


def process_chunks():
    while True:
        try:
            chunk = yield
        except GeneratorExit:
            finish_up()
            return
        else:
            do_whatever_with(chunk)

with closing(process_chunks()) as coroutine:

    # Get the coroutine to the first yield
    coroutine.next()

    FTP.retrbinary(command, callback=coroutine.send)
# coroutine.close() #  called by exiting the block

感谢你向我介绍协程。不幸的是,那个例子在我看来似乎是一种更冗长的方式来表达FTP.retrbinary(command, callback=do_whatever_with) - Nathan Buesgens
@natb1:如果do_whatever_with是一个简单的函数,那么就可以这样做。但是,如果在此处放置一个依赖于协程状态的任意代码块,则不行。在实际情况中,如果它简化为FTP.retrbinary(command, callback=do_whatever_with),那么迭代器也将成为不必要的累赘。 - user2357112
你如何指定块大小? - César
@César:FTP.retrbinary接受一个可选的第三个参数(https://docs.python.org/3/library/ftplib.html#ftplib.FTP.retrbinary),用于指定最大块大小。在Python 3中,您可以通过名称指定它为“blocksize”;在Python 2中,我认为它是仅限位置的。 - user2357112
@user2357112 哇,我怎么会错过那个?非常感谢你! - César
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接