簡體   English   中英

獲取 Python 中生成器的第 n 項

[英]Get the nth item of a generator in Python

是否有更簡潔的語法編寫方式?

gen = (i for i in xrange(10))
index = 5
for i, v in enumerate(gen):
    if i is index:
        return v

生成器應該有一個gen[index]表達式似乎幾乎很自然,它充當一個列表,但在功能上與上面的代碼相同。

一種方法是使用itertools.islice

>>> gen = (x for x in range(10))
>>> index = 5
>>> next(itertools.islice(gen, index, None))
5

您可以這樣做,使用count作為示例生成器:

from itertools import islice, count
next(islice(count(), n, n+1))

我認為最好的方法是:

next(x for i,x in enumerate(it) if i==n)

it是你的迭代器, n是索引)

它不需要您添加導入(例如使用itertools的解決方案),也不需要一次將迭代器的所有元素加載到內存中(例如使用list的解決方案)。

注意 1:如果您的迭代器少於 n 個項目,此版本會引發StopIteration錯誤。 如果你想得到None ,你可以使用:

next((x for i,x in enumerate(it) if i==n), None)

注意 2:對next的調用中沒有括號。 這不是列表推導,而是生成器推導,它不會比其第 n 個元素更遠地消耗原始迭代器。

我反對將生成器視為列表的誘惑。 簡單但幼稚的方法是簡單的單行:

gen = (i for i in range(10))
list(gen)[3]

但請記住,生成器不像列表。 他們不會將中間結果存儲在任何地方,因此您不能倒退。 我將通過 python repl 中的一個簡單示例來演示該問題:

>>> gen = (i for i in range(10))
>>> list(gen)[3]
3
>>> list(gen)[3]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range

一旦您開始通過生成器獲取序列中的第 n 個值,生成器現在處於不同的狀態,並且嘗試再次獲取第 n 個值將返回不同的結果,這可能會導致您的錯誤代碼。

讓我們根據問題中的代碼看另一個示例。

人們最初會期望以下打印4兩次。

gen = (i for i in range(10))
index = 4
for i, v in enumerate(gen):
    if i == index:
        answer = v
        break
print(answer)
for i, v in enumerate(gen):
    if i == index:
        answer = v
        break
print(answer)

但是在repl中輸入這個,你會得到:

>>> gen = (i for i in range(10))
>>> index = 4
>>> for i, v in enumerate(gen):
...     if i == index:
...             answer = v
...             break
... 
>>> print(answer)
4
>>> for i, v in enumerate(gen):
...     if i == index:
...             answer = v
...             break
... 
>>> print(answer)
9

祝你好運追蹤那個錯誤。


正如所指出的,如果生成器無限長,您甚至無法將其轉換為列表。 表達式list(gen)永遠不會完成。

有一種方法可以在無限生成器周圍放置一個延遲評估的緩存包裝器,使其看起來像一個可以隨意索引的無限長列表,但這值得它自己的問題和答案,並且會對性能產生重大影響。

我首先想到的是:

gen = (i for i in xrange(10))
index = 5

for i, v in zip(range(index), gen): pass

return v

如果n在創作時已知,則可以使用解構。 例如獲得第三項:

>>> [_, _, third, *rest] = range(10)
>>> third
2
>>> rest
[3, 4, 5, 6, 7, 8, 9]

我的解決方案是:

[a for a in range(n-1) if next(gen) and False ]
return next(gen) 

由於next(gen) and False始終為 false,因此列表推導式除了執行next(gen) n-1 次之外什么都不做。

在我的測試中,它和使用itertools.islice一樣快

建立在@Madlozoz 的答案之上,但擁有強大的海象運算符

>>> gen = (x ** 2 for x in itertools.count())
>>> [v := next(gen) for _ in range(10)]
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
>>> v
81

我不喜歡這個(和 Madlozoz 的)解決方案的一點是,我們構建潛在的巨大列表只是為了立即丟棄它。 所以也許最有效的事情是一個簡單for循環:

# `gen` continues from the previous snippet
>>> for _ in range(3):
...     v = next(gen)
...
>>> print(v)
144

以額外的一行為代價,我們還可以在分配上節省一些進程滴答並將它們用於包裝器 class:

class IterIndexer:

    def __init__(self, iter_):
        self.iter = iter_

    def __getitem__(self, i):
        for _ in range(i - 1):
            next(self.iter)
        return next(self.iter)


gen = (x ** 2 for x in itertools.count())
gen = IterIndexer(gen)
print(gen[14])
169

正確包裝它會更酷,這樣您就可以對所有內容使用包裝器實例而不是原始生成器或迭代器,但這是另一個問題 =)

也許您應該詳細說明一個實際用例。

>>> gen = xrange(10)
>>> ind=5 
>>> gen[ind]
5

您可以將生成器轉換為列表,然后像平常一樣使用索引:

>>> [i for i in range(10)][index]
5

最好使用的是:示例:

a = gen values ('a','c','d','e')

所以答案是:

a = list(a) -> this will convert the generator to a list (it will store in memory)

那么當你想去特定的索引時,你會:

a[INDEX] -> and you will able to get the value its holds 

如果您只想知道計數或執行不需要存儲在內存中的操作,最佳做法是: a = sum(1 in i in a) -> 這將計算您擁有的對象數

希望我讓它更簡單。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM