【问题标题】:Python Splitting a Generator Yield into Two PartsPython 将生成器产量分成两部分
【发布时间】:2019-04-10 22:45:30
【问题描述】:

我可以使用产生两个值的生成器:

def get_document_values():
    docs = query_database()  # returns a cursor to database documents
    for doc in docs:
        # doc is a dictionary with ,say, {'x': 1, 'y': 99}
        yield doc['x'], doc['y']

我有另一个函数process_x,我无法更改它可以将生成器作为输入来处理所有文档的所有x(如果产生了一个元组,那么它只处理元组并忽略其他元素):

X = process_x(get_document_values())  # This processes x but ignores y

但是,我还需要存储来自生成器的所有 y 值。我唯一的解决方案是执行两次get_document_values

Y = [y for x,y in get_document_values()]  #Throw away x
X = process_x(get_document_values())      #Throw away y

这在技术上是可行的,但是当有很多文档要处理时,可能会在数据库中插入一个新文档,并且XY 的长度会有所不同。 XY 之间需要一对一的映射,我只想调用一次 get_document_values 而不是两次。

我考虑过类似的事情:

Y = []

def process_y(doc_generator):
    global Y
    for x,y in doc_generator:
        Y.append(y)
        yield x

X = process_x(process_y(get_document_values()))

但是:

  1. 这感觉不像蟒蛇
  2. Y 需要声明为全局变量

我希望有一种更简洁、更 Pythonic 的方式来做到这一点。

更新

实际上,get_document_values 将返回 x 的值,这些值太大而无法集中存储到内存中,process_x 实际上正在减少内存需求。所以,不可能缓存所有的x。不过,缓存所有 y 没问题。

【问题讨论】:

    标签: python python-3.x generator


    【解决方案1】:

    您在调用时已经将所有值缓存到一个列表中:

    all_values = [(x,y) for x,y in get_document_values()] #or list(get_document_values())
    

    您可以使用以下方法获取 y 值的迭代器:

    Y = map(itemgetter(1), all_values)
    

    对于x的简单使用:

    X = process_x(map(itemgetter(0), all_values))
    

    另一种选择是将生成器分开,例如:

    def get_document_values(getter):
        docs = query_database()  # returns a cursor to database documents
        for doc in docs:
            # doc is a dictionary with ,say, {'x': 1, 'y': 99}
            yield getter(doc)
    
    from operator import itemgetter
    X = process_x(get_document_values(itemgetter("x")))
    Y = list(get_document_values(itemgetter("y")))
    

    这样你将不得不进行两次查询,如果你找到一种查询一次并复制光标的方法,那么你也可以抽象它:

    def get_document_values(cursor, getter):
        for doc in cursor:
            # doc is a dictionary with ,say, {'x': 1, 'y': 99}
            yield getter(doc)
    

    【讨论】:

    • 漂亮而简单。但可能不使用all 作为变量名。
    • @Chris_Rands 对,缺少 (...)。或者只是all_values = list(get_document_values())
    • 实际上,get_document_values 将返回 x 的值,这些值太大而无法存储到内存中,process_x 实际上正在减少内存需求。所以,不可能缓存所有的x。不过缓存所有y 很好。也许我误解了解决方案?
    • @slaw,那么更好的选择是将生成器分开,我会更新答案
    【解决方案2】:

    无需保存数据:

    def process_entry(x, y):
        process_x((x,))
        return y
    
    ys = itertools.starmap(process_entry, your_generator)
    

    请记住,只有当您获得y 值时,才会处理其对应的x 值。

    如果您同时使用两者,则将两者都作为元组返回:

    def process_entry(x, y):
        return next(process_x((x,))), y
    

    【讨论】:

    • process_x 的结果存储在哪里?请注意,在我的问题中,处理所有x 的结果存储在X 中。
    • 我注意到一个问题。 process_x 将生成值 x 的生成器作为输入,而不是直接生成 x。所以,我认为这行不通,但也许我误解了starmap 的工作方式。
    • starmap 只是将参数解包到函数中。
    • + 显然是map :)
    【解决方案3】:

    您可能希望使用itertools.tee 从一个迭代器创建两个迭代器,然后将一个迭代器用于process_x,第二个用于另一个目的

    【讨论】:

    • 应该注意的是,除非两个迭代器交错使用(在 OP 的示例中没有),否则 tee 将在内部缓存列表/队列或类似中的所有值,所以没有真正的优于将它们预先存储在列表中。
    【解决方案4】:

    可能不是pythonic,但是如果允许稍微更改主生成器并利用其函数属性,您可以作弊:

    from random import randrange
    def get_vals():
            # mock creation of a x/y dict list
            docs =[{k: k+str(randrange(50)) for k in ('x','y')} for _ in range(10)]
            # create a function list attribute
            get_vals.y = []
            for doc in docs:
                # store the y value into the attribute
                get_vals.y.append(doc['y'])
                yield doc['x'], doc['y']  
                # if doc['y'] is purely for storage, you  might opt to not yield it at all.
    

    测试一下:

    # mock the consuming of generator for process_x            
    for i in get_vals():
        print(i)    
    # ('x13', 'y9'), ('x15', 'y40'), ('x41', 'y49')...
    
    # access the ys stored in get_val function attribute after consumption
    print(get_vals.y)
    # ['y9', 'y40', 'y49', ...]
    
    # instantiate the generator a second time a la process_x...
    for i in get_vals():
        print(i)
    # ('x18', 'y0'), ('x6', 'y35'), ('x24', 'y45')...
    
    # access the cached y again
    print(get_vals.y)
    # ['y0', 'y35', 'y45', ...] 
    
    1. 这基本上会缓存您的 y 值,因为生成器会为每次调用输出其 x。
    2. 它消除了你的global关键字
    3. 您可以确定 x/y 映射是准确的。

    有些人可能会认为这是一种 hack,但我想将其视为一项功能,因为 Python 中的所有内容都是一个对象,它允许您摆脱这种情况...

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-12-06
      • 1970-01-01
      • 2021-09-13
      • 1970-01-01
      • 1970-01-01
      • 2020-01-18
      相关资源
      最近更新 更多