首页 > 解决方案 > Python - 迭代生成器表达式会影响迭代另一个生成器表达式的顺序吗?

问题描述

我正在使用两个不同的 for 循环迭代两个不同的生成器。但是我可以看到通过一个生成器表达式的迭代正在影响另一个生成器表达式的迭代顺序。

虽然我理解并希望这是不可能的,但不知道为什么我会遇到这种奇怪的行为。

we=KeyedVectors.load_word2vec_format('../input/nlpword2vecembeddingspretrained/GoogleNews-vectors-negative300.bin', binary='True')


data1=(['this','is','an','example','text1'],['this','is','an','example','text2'],....)

data2=(['test data1','test data2'],['test data3','test data4'],....)

txt_emb=(sum([we[token] for token in doc if token in we.key_to_index])/len(doc) for doc in data1)

phr_emb=([sum([we[token] for token in phrase.split(' ') if token in we.key_to_index])/len(phrase.split(' ')) for phrase in phrases]for phrases in data2)

for i in txt_emb:
    print(i)
    break
for j in phr_emb:
    print(j)
    break

txt_emb:

([-0.06002714 0.00999211 0.0358354 ....],........[0.07940271 -0.02072765 -0.03981323...])

phr_emb:

([数组([-0.13269043,0.03266907,...]),数组([0.04994202,0.15716553,...])],

[数组([-0.06970215,0.01029968,...]),数组([0.02503967,0.13970947,...])],.......)

这里 txt_emb 是一个生成器表达式,每个迭代都是一个列表。

phr_emb 是一个生成器表达式,每个迭代都是一个列表,每个列表包含不同数量的数组(比如 2-6)。

当我在上面的代码中首先迭代 txt_emb 时,我得到了 txt_emb 的第一个元素(索引 0 处的列表),这是预期的。同样,当我遍历 phr_emb 时,我希望得到第一个元素(索引 0 处的列表),但我得到第二个元素(索引 1 处的列表)。

同样,如果我继续再次迭代 txt_emb,我得到第三个元素(索引 2 处的列表),而不是获取 txt_emb 索引 1 处的元素,因为在此之前我只迭代了 txt_emb 一次。

当我压缩两个生成器表达式 txt_emb 和 phr_emb 并尝试遍历它时,我遇到了类似的问题。

我在 kaggle 笔记本中运行所有这些。但是如果我在笔记本的不同单元格中分别迭代两个生成器表达式,那么我会按预期顺序获得元素。

标签: pythonnlpiteratorgenerator

解决方案


从您的评论来看,这似乎是您的两个生成器表达式从其他一些共享迭代器(可能是另一个生成器表达式)中提取数据的问题。当第一个生成器表达式前进时,它从第三个迭代器中获取数据,这使得它对第二个生成器表达式不可用。

您可以使用如下更简单的代码重新创建此问题:

data = range(10) # our underlying data
data_iterator = iter(data) # our shared iterator, which could be a generator expression

doubles = (x * 2 for x in data_iterator) # first generator expression
squares = (x * x for x in data_iterator) # second generator expression

print(next(doubles), next(doubles), next(doubles)) # prints 0 2 4
print(next(squares), next(squares), next(squares)) # prints 9 16 25, not 0 1 4 as you might expect

如果您从一个生成器表达式中获取一些值,则在另一个生成器表达式中将跳过相应的值。那是因为他们每个人都data_iterator在后台推进共享,这只会遍历列表中的每个值一次。

解决方案是为每个生成器创建单独的迭代器(例如 的多个版本data_iterator,或者如果重新计算很麻烦或耗时,则将其转储到像列表一样的序列中,以便可以重复迭代。

例如,我们可以像这样转储data_iteratordata_list中,然后从列表中构建生成器表达式:

data = range(10)
data_iterator = iter(data)
data_list = list(data_iterator)    # this can be iterated upon repeatedly

doubles = (x * 2 for x in data_list)
squares = (x * x for x in data_list)

print(next(doubles), next(doubles), next(doubles)) # prints 0 2 4
print(next(squares), next(squares), next(squares)) # prints 0 1 4 as expected

现在,将数据存储在这样的列表中可能会占用比您想要的更多的内存。生成器和生成器表达式的优点之一是它们允许的惰性计算。如果您想保持这种惰性计算方法,并且只需要几个值在另一个生成器之前可用,因为它们主要是并行消耗的(例如 by zip),那么itertools.tee可能正是您所需要的。

import itertools

data = range(10)
data_iterator = iter(data)
data_it1, data_it2 = itertools.tee(data_iterator) # two iterators that will yield the same results

doubles = (x * 2 for x in data_it1)
squares = (x * x for x in data_it2)

for d, s in zip(doubles, squares):  # consume the values in parallel
    print(d, s)

如果您计划在启动另一个生成器之前完全消耗一个生成器,则仍然可以使用返回的迭代器,但在这种情况下它们的效率要低得多(将整个中间迭代器转储到一个列表中可能会更好tee)。


推荐阅读