将功能转换为发电机

时间:2016-07-17 17:32:13

标签: python generator

通过以下方法,我可以列出Google云端硬盘帐户中的所有文件:

def listAllFiles(self):
    result = [];
    page_token = None;

    while True:
        try:
            param = {"q" : "trashed=false", "orderBy": "createdTime"};
            if page_token: param['pageToken'] = page_token;
            files = self.service.files().list(**param).execute();

            result.extend(files["files"]);
            page_token = files.get('nextPageToken');
            if not page_token: break;

        except errors.HttpError as error:
            print('An error occurred:', error);
            break; # Exit with empty list

    return result;

为了更好的运行时间,我想从这个方法返回一个生成器。我对Python很陌生,所以我不知道如何做到这一点。

来自文件服务的execute方法总是返回100个项目,如果它返回page_token,则还有更多要获取的项目。如果我可以迭代生成器来获取已经获取的项目并且同时从服务中获取下一个项目,那将是很好的。我希望你明白我的意思......

这可能吗?我如何重写此方法以获得所描述的功能?

3 个答案:

答案 0 :(得分:3)

您可以通过简单地生成单个文件路径来重写您的函数以充当生成器。

未测试:

def listAllFiles(self):
    result = []
    page_token = None

    while True:
        try:
            param = {"q" : "trashed=false", "orderBy": "createdTime"}
            if page_token:
                param['pageToken'] = page_token
            files = self.service.files().list(**param).execute()

            # call future to load the next bunch of files here!
            for f in files["files"]:
                yield f
            page_token = files.get('nextPageToken')
            if not page_token: break

        except errors.HttpError as error:
            print('An error occurred:', error)
            break

如果您不进一步并行使用chapelo's answer。产生所有可用文件的列表将允许协程继续,从而开始同时获取下一个文件列表。

用期货

预加载下一批

现在,您仍然没有同时加载下一批文件。 为此,如上面的代码所述,您可以执行future以便同时收集下一个文件列表。 当您的屈服项目被消耗(并且您的功能继续执行)时,您将展望您的未来,看看结果是否已经存在。如果没有,你必须等待(如前所述),直到结果到来。

由于我没有您的代码,我无法说明此代码是否有效(或者语法上是否正确),但您可以将其作为起点

import concurrent.futures

def load_next_page(self, page_token=None):
    param = {"q" : "trashed=false", "orderBy": "createdTime"}
    if page_token:
        param['pageToken'] = page_token

    result = None
    try:
        files = self.service.files().list(**param).execute()
        result = (files.get('nextPageToken'), files["files"])
    except errors.HttpError as error:
        print('An error occurred:', error)
    return result

def listAllFiles(self):
    with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:

        future = executor.submit(self.load_next_page, 60) 

        while future:
            try:
                result = future.result()
                future = None
                if not result:
                    break
                (next_page_token, files) = result            
            except Exception as error:
                print('An error occured:', error)
                break
            if next_page_token:
                future = executor.submit(self.load_next_page, next_page_token, 60) 
            # yield from files
            for f in files:
                yield f

使用队列的生产者/消费者并行化

评论中也提到的另一个选择是使用Queue。您可以修改函数以返回由函数生成的线程填充的队列。这应该比仅预加载下一个列表更快,但也会产生更高的实现开销。

我个人会建议采用未来的路径 - 如果表现足够的话。

答案 1 :(得分:1)

如果一次产生每个文件,则阻止生成器。但是,如果您生成生成器已准备好的整个列表,则在处理文件列表时,生成器将为您准备另一个列表:

而不是迈克尔的建议

for f in files["files"]:
    yield f

尝试立即生成整个列表,并在收到文件时处理整个文件列表:

yield files["files"]

考虑这个简单的例子:

from string import ascii_uppercase as letters, digits
lst_of_lsts = [[l+d for d in digits] for l in letters]

def get_a_list(list_of_lists):
    for lst in list_of_lists:
        yield lst  # the whole list, not each element at a time

gen = get_a_list(lst_of_lsts)

print(gen.__next__()) # ['A0', 'A1', 'A2', 'A3', 'A4', 'A5', 'A6', 'A7', 'A8', 'A9']

print(gen.__next__()) # ['B0', 'B1', 'B2', 'B3', 'B4', 'B5', 'B6', 'B7', 'B8', 'B9']

print(gen.__next__()) # ['C0', 'C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'C8', 'C9']

# And so on...

答案 2 :(得分:0)

您将不得不更改脚本的流程。您不需要一次返回所有文件,而是需要yield个别文件。 这将允许您处理在后台获取结果。

编辑:后续结果的获取对于调用函数是透明的,它看起来会花费更长的时间。基本上,一旦当前文件列表全部归功于调用函数,您将获得下一个列表,并从该列表开始屈服,重复直到没有更多文件要从Google云端硬盘列出。

我强烈建议您阅读What does the "yield" keyword do in Python?以了解生成器和放大器背后的概念。 yield声明。