2010-02-20 21 views
36

Existe-t-il une manière plus concise et plus synthétique d'écrire ce qui suit?Récupère le nième élément d'un générateur en Python

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

Il semble presque naturel qu'un générateur doit avoir une expression gen[index], qui agit comme une liste, mais est fonctionnellement identique au code ci-dessus.

+8

Vous ne voulez pas 'is' dans cette situation (ou de nombreuses situations du tout). 'is' est pour comparer l'identité, pas l'égalité. Vous voulez '=='. Cela fonctionnera probablement dans ce cas, mais seulement par coïncidence et détails de mise en œuvre. –

+1

Puisque j'utilise des nombres entiers, comment cela pourrait-il ne pas fonctionner? Est-ce une bonne pratique de s'attendre à ce que l'objet 'index' implémente' __eq__' dans des cas comme celui-ci? (Ceci est hors sujet ...) –

+2

Essayez '1000 est 500 + 500', il sera (probablement)' False'. Voir, par exemple, http://stackoverflow.com/questions/306313/python-is-operator-behaves-unexpectedly-with-integers –

Répondre

35

une méthode serait d'utiliser itertools.islice

>>> next(itertools.islice(xrange(10), 5, 5 + 1)) 
5 
+3

Je ne connaissais même pas la fonction 'next()'. Merci! –

+3

Remplacer '5 + 1' par' None' cela fonctionne aussi, et lit plus simple –

-1

Peut-être devriez-vous élaborer davantage sur un cas d'utilisation réel.

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

J'ai édité 'xrange (10)' à '(i pour i dans xrange (10))'. Il s'avère que cette syntaxe fonctionne pour 'xrange' car ce n'est pas vraiment un générateur ... –

+5

' xrange' est un générateur de prédécesseurs, et retourne un objet xrange, qui implémente le protocole de séquence complète. –

14

Vous pouvez le faire, en utilisant count comme générateur d'exemple:

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

Quelle est la version de Python? Le code ci-dessus me donne l'erreur 'AttributeError: l'objet 'itertools.islice' n'a pas d'attribut 'next'' dans 3.3. – LarsH

+0

En Python 3x, remplacez 'next' par' __next __() ', c'est-à-dire' islice (count, n, n = 1) .__ next __() ' – Mohammed

+2

Il est donc préférable d'utiliser' next (islice (count(), n , n + 1)) '. –

-2

vous pouvez simplement convertir le générateur dans une liste et utiliser l'index comme d'habitude:

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

Je discuterais contre la tentation de traiter les générateurs comme des listes. L'approche simple mais naïve est le simple-liner:

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

Mais rappelez-vous, les générateurs ne sont pas comme les listes. Ils ne stockent leurs résultats intermédiaires nulle part, donc vous ne pouvez pas revenir en arrière. Je démontrerai le problème avec un exemple simple dans le rempl python:

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

Une fois que vous commencez à passer par un générateur pour obtenir la valeur n-ième dans la séquence, le générateur est maintenant dans un état différent, et en essayant de obtenir la nième valeur à nouveau vous retournera un résultat différent, ce qui entraînera probablement un bug dans votre code.

Jetons un coup d'oeil à un autre exemple, basé sur le code de la question.

On s'attendait initialement à ce que les éléments suivants impriment deux fois 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) 

mais tapez ceci dans le rempl et vous obtenez:

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

Bonne chance que le traçage bug vers le bas.

0

La première chose qui vient à mon esprit était:

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

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

return v