将项添加回可迭代(yield / generator)

时间:2014-02-18 19:56:27

标签: python generator yield

我认为这是一个使用yield的好时刻,但我被卡住了。

当某些内容失败时,我想将该项目发回发电机。我已经读到这是可能的,所以我真的很想使用我的第一台发电机。

states = ["IL", "NY", "NJ"]
for state in states:
    ok = do something
    if not ok:
        *add state back as the first-to-deal with in the generator*

如何在这种情况下使用发电机?

3 个答案:

答案 0 :(得分:3)

您可能指的是coroutine,它利用 yield表达式。它有点像这样:

def co_gen(li):
    for x in li:
        bad = yield x
        if bad is not None:
            print('ack! {}'.format(bad))
            #other error handling

和(人为)用法:

states = ["IL", "NY", "NJ"]

gen = co_gen(states)

for x in gen:
    print('processing state: {}'.format(x))
    if x == 'NY':
        y = gen.send('Boo, Yankees!')
        print( 'processing state after error: {}'.format(y))

# processing state: IL
# processing state: NY
# ack! Boo, Yankees!
# processing state after error: NJ

突出点 - 正常yield行为会将None分配给bad。如果它不是None,那么send的东西就被编入了生成器。

当我们send进入生成器时,恢复操作直到它到达下一个yield表达式。所以记住这一点 - 协程中的上述控制流程不是我称之为“标准”的,因为错误块中没有yield完成。

这是一个比你正在谈论的更像一个协程:

def co_gen(li):
    for x in li:
        bad = yield x
        while bad is not None:
            print('error in generator: {}'.format(bad))
            yield
            bad = yield bad

gen = co_gen(states)

for x in gen:
    print('processing state: {}'.format(x))
    if random.choice([0,1]):
        gen.send(x) #discard first yield
        print( 'error: trying {} again'.format(x) )

# processing state: IL
# error in generator: IL
# error: trying IL again
# processing state: IL
# processing state: NY
# error in generator: NY
# error: trying NY again
# processing state: NY
# processing state: NJ

我们send我们的状态回到了生成器,并且它一直在让它停止发送它。

答案 1 :(得分:0)

def ok(i):
    from random import randint
    return bool(randint(0,1))


def mygen(iterable):
    def helper(iterable):
        for i in iterable:
            elem = yield i
            if elem:
                iterable.append(elem)

    it = helper(iterable)
    sendBack = False
    while True:
        try:
            if sendBack:
                print "Sending back {0}".format(i)
                i = it.send(i)
            else:
                i = it.send(None)
            if ok(i):
                sendBack = False
                yield i
            else:
                sendBack = True

        except StopIteration:
            break

x = range(10)

print list(mygen(x))
#Sending back 1
#Sending back 5
#Sending back 7
#Sending back 1
#Sending back 7
#[0, 2, 3, 4, 6, 8, 9, 5, 1, 7]

两个生成器,如果某些函数的返回值的计算结果为false,则返回该值。 (在这种情况下是随机的)。

答案 2 :(得分:0)

虽然可以使用常规生成器以及gen.send()sent_back = yield x执行您所要求的操作,但您的代码将非常复杂。编写自己的iterator type可能更容易,它支持send以外的方法来获取“已发回”项目:

class SendBackIter(object):
    def __init__(self, iterable):
        self.iterator = iter(iterable)
        self.sent_back = []

    def __iter__(self):
        return self

    def __next__(self):
        if self.sent_back:              # if the stack is not empty...
            return self.sent_back.pop() # return the last item from the sent_back stack
        return next(self.iterator)      # otherwise return an item from our iterator

    def send_back(self, obj):
        self.sent_back.append(obj)

如果您只需要处理重复迭代中的项目,那么您可以使其更简单:

def RepeatableIter(object);
    def __init__(self, iterable):
        self.iterator = iter(iterable)
        self.last_item = None
        self.repeat = False   # client code can set this to True to repeat the last value

    def __iter__(self):
        return self

    def __next__(self):
        if self.repeat:
            self.repeat = False # only repeat once, by default
        else:
            self.last_item = next(self.iterator)

        return self.last_item

以下是您可以使用最后一个版本的方法:

it = RepeatableIter(["foo", "bar", "baz"])
for item in it:
    if is_not_ok(item):
        it.repeat = True # this means we will get the same item on the next iteration
    else:
        do_something(item)