Python 如何用过滤器包裹发电机?

Python 如何用过滤器包裹发电机?,python,generator,Python,Generator,我有一系列连接的发电机,我想创建一个过滤器,可以用来包装其中一个发电机。此筛选器包装器应将生成器和函数作为参数。如果传入流中的数据项未通过过滤器的要求,则应将其向下传递到下一个生成器,而无需经过包装的生成器。我在这里举了一个工作示例,可以更清楚地说明我正在努力实现的目标: import functools is_less_than_three = lambda x : True if x < 3 else False def add_one(numbers): print("n

我有一系列连接的发电机,我想创建一个过滤器,可以用来包装其中一个发电机。此筛选器包装器应将生成器和函数作为参数。如果传入流中的数据项未通过过滤器的要求,则应将其向下传递到下一个生成器,而无需经过包装的生成器。我在这里举了一个工作示例,可以更清楚地说明我正在努力实现的目标:

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(generator1, filter_):
    @functools.wraps(generator1)
    def wrapped(generator2):
        for data in generator2:
            if filter_(data):
                yield from generator1([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
导入工具
_小于_小于_三=λx:如果x<3则为真,否则为假
def添加一个(数字):
打印(“创建新生成器”)
对于数字中的数字:
收益率+1
def包装器(发电机1、过滤器1):
@functools.wrapps(generator1)
def包裹(发电机2):
对于generator2中的数据:
如果过滤器(数据):
发电机1的产量([数据])
其他:
产量数据
退货包装
将一个添加到三个数字中=包装器(添加一个,是三个)
答案=在数字上加一个小于三个(范围(6))
有关答案中的答案:
打印(答案)
#已创建新的生成器
#1
#已创建新的生成器
#2
#已创建新的生成器
#3
#3
#4
#5
问题是它需要为每个数据项创建一个新的生成器。一定有更好的办法吗?我也尝试过使用itertools.tee并拆分生成器,但当生成器以不同的速率生成值时,这会导致内存问题(确实如此)。如何在不重新创建生成器和不引起内存问题的情况下完成上述代码的工作

编辑以在下面添加背景信息

作为输入,我将接收大的视频流。视频流可能结束,也可能不结束(可能是网络摄像头)。用户可以选择对视频帧执行哪些图像处理步骤,因此功能的顺序和数量将发生变化。随后,各功能应能够将彼此的输出作为输入

我使用了一系列的发电机来完成这项工作。生成器/函数的输入输出比是可变的,可以是1:n、1:1或n:1(例如,从要单独处理的图像中提取多个对象(子图像)

目前,这些生成器采用了一些在它们之间重复的参数(非干式),我正试图通过将它们重构为单独的生成器或包装器来减少参数的数量。其中一个比较困难的是数据流上的过滤器,用于确定是否应将函数应用于该帧(该函数可能是cpu密集型的,而不是所有帧都需要)

参数的数量使得用户更难理解函数的用法。这也使我更加困难,因为每当我想要更改一个公共参数时,我必须为所有函数编辑它

edit2在示例代码中将函数重命名为generator,以使其更加清晰

edit3解决方案 谢谢你@Blckknght。这可以通过创建一个无限迭代器来解决,该迭代器将局部变量的值传递给生成器。我稍微修改了我的示例,将add_one更改为1:n生成器,而不是1:1生成器,以显示此解决方案如何也适用于1:n生成器

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:
        if number == 0:
            yield number - 1
            yield number
        else:
            yield number

def wrapper(generator1, filter_):
    @functools.wraps(generator1)
    def wrapped(generator2):
        local_variable_passer = generator1(iter(lambda: data, object()))
        for data in generator2:
            if filter_(data):
                next_data = next(local_variable_passer)
                if data == 0:
                    yield next_data
                    next_data = next(local_variable_passer)
                    yield next_data
                else:
                    yield next_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
#0
#1
#2
#3
#3
#4
#5
导入工具
_小于_小于_三=λx:如果x<3则为真,否则为假
def添加一个(数字):
打印(“创建新生成器”)
对于数字中的数字:
如果数字==0:
产量数字-1
产量
其他:
产量
def包装器(发电机1、过滤器1):
@functools.wrapps(generator1)
def包裹(发电机2):
局部变量\u passer=generator1(iter(lambda:data,object())
对于generator2中的数据:
如果过滤器(数据):
下一个数据=下一个(本地变量)
如果数据==0:
产生下一个数据
下一个数据=下一个(本地变量)
产生下一个数据
其他:
产生下一个数据
其他:
产量数据
退货包装
将一个添加到三个数字中=包装器(添加一个,是三个)
答案=在数字上加一个小于三个(范围(6))
有关答案中的答案:
打印(答案)
#已创建新的生成器
#-1
#0
#1
#2
#3
#3
#4
#5

体系结构是一个条件映射-因此,每个项目必须单独映射。这意味着
功能
应该接收一个数字,而不是多个数字


只要存在无状态1:1连接,就使用函数而不是生成器

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)
由于这仍然是一个1:1连接,因此可以与以前的
条件映射一起使用

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连接,则可以使用生成生成器/iterable的协同程序

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))

架构是一个条件映射,因此,每个项目必须单独映射。这意味着
功能
应该接收一个数字,而不是多个数字


只要存在无状态1:1连接,就使用函数而不是生成器

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)
由于这仍然是一个1:1连接,因此可以与以前的
条件映射一起使用

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连接,则可以使用生成生成器/iterable的协同程序

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))

据我所知,您有一个视频帧流,您正在尝试创建一个处理函数管道来修改该流。不同的处理函数可能会更改帧数,因此单个输入帧可能会导致多个输出帧,或者在生成单个输出帧之前可能会消耗多个输入帧。有些函数可能是1:1,但这不是您可以信赖的

当前实现使用生成器函数进行所有处理。输出函数
_sentinel = object()
class PeekableIterator:
    def __init__(self, input_iterable):
        self.iterator = iter(input_iterable)
        self.next_value = next(self.iterator, _sentinel)

    def __iter__(self):
        return self

    def __next__(self):
        if self.next_value != _sentinel:
            return_value = self.next_value
            self.next_value = next(self.iterator, _sentinel)
            return return_value
        raise StopIteration

    def peek(self):                 # this is not part of the iteration protocol!
        if self.next_value != _sentinel:
            return self.next_value
        raise ValueError("input exhausted")

def selective_processing_Nto1(processing_func, condition, input_iterable):
    peekable = PeekableIterator(input_iterable)
    processing_iter = processing_func(peekable)
    while True:
        try:
            value = peekable.peek()
            print(value, condition(value))
        except ValueError:
            return
        try:
            yield next(processing_iter) if condition(value) else next(peekable)
        except StopIteration:
            return