如何使用过滤器包装生成器?

问题描述 投票:0回答:1

我有一系列已连接的生成器,我想创建一个可用于包装其中一个生成器的过滤器。该过滤器包装器应将生成器和函数作为参数。如果传入流中的数据项未通过过滤器的要求,则应将其下游传递到下一个生成器,而不必通过包装的生成器。我在这里做了一个工作示例,应该使我更清楚地了解要实现的目标:

import functools

is_less_than_three = lambda x : True if x < 3 else False

def add_one(numbers):
    print("new generator created")
    for number in numbers:
        yield number + 1

def wrapper(function, filter_):
    @functools.wraps(function)
    def wrapped(generator):
        for data in generator:
            if filter_(data):
                yield from function([data])
            else:
                yield data
    return wrapped

add_one_to_numbers_less_than_three = wrapper(add_one, is_less_than_three)
answers = add_one_to_numbers_less_than_three(range(6))
for answer in answers:
    print(answer)

#new generator created
#1
#new generator created
#2
#new generator created
#3
#3
#4
#5

问题在于,它需要为每个数据项创建一个新的生成器。肯定有更好的办法?我也尝试过使用itertools.tee并拆分生成器,但是当生成器以不同速率生成值时(这确实会),这会导致内存问题。如何在不重新创建生成器且不引起内存问题的情况下完成上述代码的工作?

python generator
1个回答
0
投票

只要存在无状态的1:1连接,请使用function而不是生成器。

def add_one(number):  # takes one number
    return number + 1  # provides one number

def conditional_map(function, condition):
    @functools.wraps(function)
    def wrapped(generator):
        return (
            function(item) if condition(item)
            else item for item in generator
        )
    return wrapped

for answer in conditional_map(add_one, lambda x: x < 3)(range(6)):
    print(answer)

如果必须将数据传递给有状态的“生成器”,则它是协程,应该这样设计。这意味着yield既用于接收并提供数据。

from itertools import count

def add_increment(start=0):
    # initially receive data
    number = yield
    for increment in count(start):
        # provide and receive data
        number = yield number + increment

由于这仍然是1:1连接,因此可以与先前的conditional_map一起使用。

mapper = add_increment()
next(mapper)  # prime the coroutine - this could be done with a decorator

for answer in conditional_map(mapper.send, lambda x: x < 3)(range(6)):
    print(answer)

如果需要1:n连接,则期望为每个输入接收一个生成器。

def add_some(number):  # takes one number
    yield number - 1
    yield number
    yield number + 1

def conditional_map(function, condition):
    @functools.wraps(function)
    def wrapped(generator):
        for data in generator:
            if filter_(data):
                yield from function(data)  # passes one *one* item
            else:
                yield data
    return wrapped

如果需要有状态的1:n连接,则可以使用产生生成器/迭代器的协程。

def add_increments(start=0):
    # initially receive data
    number = yield
    for increment in count(start):
        # provide and receive data
        number = yield (number + increment + i for i in (-1, 0, 1))
© www.soinside.com 2019 - 2024. All rights reserved.