将生成器包装为具有单个`next`调用,而不是两个步骤(__iter__ + __next__)

时间:2018-08-02 15:37:37

标签: python python-3.x generator

我从发生器接收到数量不明的记录用于后台处理。如果还有更重要的工作,我必须停止发布流程。

main流程最好描述为:

def main():
    generator_source = generator_for_test_data()  # 1. contact server to get data.
    uw = UploadWrapper(generator_source)  # 2. wrap the data.
    while not interrupt():  # 3. check for interrupts.
        row = next(uw)
        if row is None:
            return
        print(long_running_job(row))  # 4. do the work.

是否有一种无需插入__next__即可到达__iter__的方法? 有两个步骤-(1)创建一个迭代器,然后(2)对其进行迭代,看起来很笨拙。

在很多情况下,我希望将功能提交给功能管理器(mapreduce样式),但是在这种情况下,我需要带有一些设置的实例化类。因此,仅当单个功能为__next__

时,才能注册单个功能
class UploadWrapper(object):
    def __init__(self, generator):
        self.generator = generator
        self._iterator = None

    def __iter__(self):
        for page in self.generator:
            yield from page.data

    def __next__(self):
        if self._iterator is None:                # ugly bit.
            self._iterator = self.__iter__()      # 
        try:
            return next(self._iterator)
        except StopIteration:
            return None

问:有没有更简单的方法?


添加了工作样本以提高完整性:

import time
import random

class Page(object):
    def __init__(self, data):
        self.data = data


def generator_for_test_data():
    for t in range(10):
        page = Page(data=[(t, i) for i in range(100, 110)])
        yield page

def long_running_job(row):
    time.sleep(random.randint(1,10)/100)
    assert len(row) == 2
    assert row[0] in range(10)
    assert row[1] in range(100, 110)
    return row

def interrupt():  # interrupt check
    if random.randint(1,50) == 1:
        print("INTERRUPT SIGNAL!")
        return True
    return False

class UploadWrapper(object):
    def __init__(self, generator):
        self.generator = generator
        self._iterator = None

    def __iter__(self):
        for ft in self.generator:
            yield from ft.data

    def __next__(self):
        if self._iterator is None:
            self._iterator = self.__iter__()
        try:
            return next(self._iterator)
        except StopIteration:
            return None

def main():
    gen = generator_for_test_data()
    uw = UploadWrapper(gen)
    while not interrupt():  # check for job interrupt.
        row = next(uw)
        if row is None:
            return
        print(long_running_job(row))

if __name__ == "__main__":
    main()

1 个答案:

答案 0 :(得分:1)

您的UploadWrapper似乎很复杂,不仅有一个简单的解决方案。

我的第一个想法是完全放弃该类,而只使用一个函数:

def uploadwrapper(page_gen):
    for page in page_gen:
        yield from page.data

只需将uw = UploadWrapper(gen)替换为uw = uploadwrapper(gen),就可以了。

如果您坚持上课,则可以摆脱__next__()并将uw = UploadWrapper(gen)替换为uw = iter(UploadWrapper(gen)),它将起作用。

在任何一种情况下,您还必须在呼叫方中捕获StopIteration__next__()假定完成后会提高StopIteration,而不像您一样返回None。否则,它将无法与期望良好行为的迭代器一起使用,例如。 for循环。

我认为您可能对这一切应该如何融合有一些误解,因此,我将尽我所能尽力解释它应该如何运作:

__iter__()的要点是,如果您有例如。列表中,您可以通过调用iter()获得多个独立的迭代器。当您有一个for循环时,您实际上首先要获得一个带有iter()的迭代器,然后在每次循环迭代时对其调用next()。如果您有两个使用相同列表的嵌套循环,则迭代器及其位置仍然是分开的,因此不会发生冲突。 __iter__()应该为其所在的容器返回一个迭代器,或者如果它在迭代器上被调用,则应该仅返回self。从这种意义上讲,UploadWrapper不返回self中的__iter__()是错误的,因为它包装了一个生成器,因此实际上不能给出独立的迭代器。至于省去__next__()的原因,是因为在定义生成器(即在函数中使用yield)时,生成器有一个__iter__()(返回self ,它应该会)和__next__()达到您的期望。在您的原始代码中,您实际上并没有真正使用__iter__()来使用它:即使您将其重命名为其他代码,代码也可以正常工作!这是因为您永远不会在实例上调用iter(),而直接调用next()

如果您想作为一个班级“适当地”做,我想这样的话就足够了:

class UploadWrapper(object):
    def __init__(self, generator):
        self.generator = generator
        self.subgen = iter(next(generator).data)

    def __iter__(self):
        return self

    def __next__(self):
        while True:
            try:
                return next(self.subgen)
            except StopIteration:
                self.subgen = iter(next(self.generator).data)