简体   繁体   English

如何从迭代器/生成器中取出第一个元素并将其放回 Python?

[英]How to take first element from iterator/generator and put it back in Python?

I would like to take first element from iterator, analyse it, then put it back and work with iterator as if it was not touched.我想从迭代器中取出第一个元素,对其进行分析,然后将其放回去并使用迭代器,就好像它没有被触及一样。

For now I wrote:现在我写道:

def prepend_iterator(element, it):
    yield element
    for element in it:
        yield element


def peek_first(it):
    first_element = next(it)
    it = prepend_iterator(first_element, it)
    return first_element, it

first_element, it = peek_first(it)

analyse(first_element)

continue_work(it)

it is possible to write better/shorter?有可能写得更好/更短吗?

Here is an example wit itertools.tee 这是一个示例itertools.tee

import itertools
def colors():
    for color in ['red', 'green', 'blue']:
        yield color

rgb = colors()
foo, bar = itertools.tee(rgb, 2)

#analize first element
first =  next(foo)
print('first color is {}'.format(first))

# consume second tee
for color in bar:
    print(color)

output 产量

first color is red
red
green
blue

Here I present a simple method that exploits concatenation of generators.在这里,我提出了一种利用生成器串联的简单方法。

import itertools
def concat_generators(*args: Iterable[Generator]) -> Generator:
    r"""
    Concat generators by yielding from first, second, ..., n-th
    """
    for gen in args:
        yield from gen

your_generator = (i for i in range(10))
first_element = next(your_generator)

# then you could do this
your_generator = concat_generators([first_element], your_generator)
# or this
your_generator = itertools.chain([first_element], your_generator)

Note this will only work if you're pushing back non- None values. 注意这一点,如果你推背不只会工作None价值。

If you implement your generator function (which is what you have) so that you care about the return value of yield , you can "push back" on the generator (with .send() ): 如果实现生成器函数(即您拥有的函数),以便您关心yield返回值 ,则可以在生成器上“推回”(使用.send() ):

# Generator
def gen():
    for val in range(10):
        while True:
            val = yield val
            if val is None: break

# Calling code
pushed = false
f = gen()
for x in f:
    print(x)
    if x == 5:
        print(f.send(5))
        pushed = True

Here, you're printing both the x from the for loop and the return value of .send() (if you call it). 在这里,您同时打印了for循环中的x .send()的返回值(如果调用了它)。

0
1
2
3
4
5
5   # 5 appears twice because it was pushed back
6
7
8
9

This will only work let you push back once. 这只会让您后退一次。 If you want to push back more times than that, you do something like: 如果您想推回更多次,可以执行以下操作:

# Generator
def gen():
    for val in range(10):
        while True:
            val = yield val
            if val is None: break

# Generator Wrapper
class Pushable:
    def __init__(self, g):
        self.g = g
        self._next = None

    def send(self, x):
        if self._next is not None:
            raise RuntimeError("Can't pushback twice without pulling")
        self._next = self.g.send(x)

    def __iter__(self):
        try:
            while True:
                # Have to clear self._next before yielding
                if self._next is not None:
                    (tmp, self._next) = (self._next, None)
                    yield tmp
                else:
                    yield next(self.g)

        except StopIteration: return

# Calling code
num_pushed = 0
f = Pushable(gen())
for x in f:
    print(x)
    if (x == 5) and (num_pushed in [0,1,2]):
        f.send(x)
        num_pushed += 1

Produces: 生产:

0
1
2
3
4
5  # Pushed back (num_pushed = 0)
5  # Pushed back (num_pushed = 1)
5  # Pushed back (num_pushed = 2)
5  # Not pushed back
6
7
8
9

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM