Les générateurs Python sont très utiles. Ils ont des avantages par rapport aux fonctions qui renvoient des listes. Cependant, vous pourriez len(list_returning_function())
. Existe-t-il un moyen de len(generator_function())
?
UPDATE:
Bien sûr, len(list(generator_function()))
fonctionnerait .....
J'essaie d'utiliser un générateur que j'ai créé à l'intérieur d'un nouveau générateur que je crée. Dans le cadre du calcul dans le nouveau générateur, il doit connaître la longueur de l'ancien. Cependant, j'aimerais conserver les deux propriétés avec les mêmes propriétés qu'un générateur, en particulier - ne pas conserver la liste complète en mémoire, car elle peut être très longue.
UPDATE 2:
Supposons que le générateur sait sa longueur cible même à partir du premier pas. En outre, il n'y a aucune raison de conserver la syntaxe len()
. Exemple - si les fonctions dans Python sont des objets, pourrais-je attribuer la longueur à une variable de cet objet qui serait accessible au nouveau générateur?
Les générateurs n'ont pas de longueur, ils ne sont pas des collections après tout.
Les générateurs sont fonctions avec un état interne (et une syntaxe élégante). Vous pouvez les appeler plusieurs fois pour obtenir une séquence de valeurs afin de pouvoir les utiliser en boucle. Mais comme ils ne contiennent aucun élément, demander la longueur d'un générateur revient à demander la longueur d'une fonction.
si les fonctions dans Python sont des objets, ne puis-je pas affecter la longueur à une variable de cet objet qui serait accessible au nouveau générateur?
Les fonctions sont des objets, mais vous ne pouvez pas leur attribuer de nouveaux attributs. La raison en est probablement de garder un objet de base aussi efficace que possible.
Vous pouvez cependant simplement retourner (generator, length)
s'associe à partir de vos fonctions ou enveloppe le générateur dans un objet simple comme celui-ci:
class GeneratorLen(object):
def __init__(self, gen, length):
self.gen = gen
self.length = length
def __len__(self):
return self.length
def __iter__(self):
return self.gen
g = some_generator()
h = GeneratorLen(g, 1)
print len(h), list(h)
La conversion en list
suggérée dans les autres réponses est la meilleure solution si vous souhaitez toujours traiter les éléments du générateur après, mais présente un défaut: il utilise O(n) = mémoire. Vous pouvez compter les éléments dans un générateur sans utiliser autant de mémoire avec:
sum(1 for x in generator)
Bien sûr, sachez que ceci peut être plus lent que les implémentations de len(list(generator))
en commun Python), et si les générateurs sont suffisamment longs pour que la complexité de la mémoire soit importante, l'opération prendrait: Personnellement, je préfère cette solution car elle décrit ce que je veux obtenir et ne me donne rien de plus qui n’est pas nécessaire (comme une liste de tous les éléments).
Écoutez également le conseil de Delnan: si vous supprimez la sortie du générateur, il est fort probable qu'il existe un moyen de calculer le nombre d'éléments sans le faire fonctionner, ou en les comptant d'une autre manière.
Supposons que nous ayons un générateur:
def gen():
for i in range(10):
yield i
Nous pouvons envelopper le générateur, avec la longueur connue, dans un objet:
import itertools
class LenGen(object):
def __init__(self,gen,length):
self.gen=gen
self.length=length
def __call__(self):
return itertools.islice(self.gen(),self.length)
def __len__(self):
return self.length
lgen=LenGen(gen,10)
Les instances de LenGen
sont elles-mêmes des générateurs, car leur appel renvoie un itérateur.
Nous pouvons maintenant utiliser le générateur lgen
à la place de gen
, et accéder également à len(lgen)
:
def new_gen():
for i in lgen():
yield float(i)/len(lgen)
for i in new_gen():
print(i)
Vous pouvez utiliser len(list(generator_function())
. Cependant, cela utilise le générateur, mais c’est le seul moyen de savoir combien d’éléments sont générés. Donc, vous voudrez peut-être enregistrer la liste quelque part si vous souhaitez également utiliser les éléments.
a = list(generator_function())
print(len(a))
print(a[0])
Vous pouvez len(list(generator))
mais vous pourriez probablement améliorer quelque chose si vous avez vraiment l'intention de supprimer les résultats.
Vous pouvez utiliser send
comme un hack:
def counter():
length = 10
i = 0
while i < length:
val = (yield i)
if val == 'length':
yield length
i += 1
it = counter()
print(it.next())
#0
print(it.next())
#1
print(it.send('length'))
#10
print(it.next())
#2
print(it.next())
#3
Vous pouvez combiner les avantages des générateurs avec la certitude de len()
, en créant votre propre objet itérable:
class MyIterable(object):
def __init__(self, n):
self.n = n
def __len__(self):
return self.n
def __iter__(self):
self._gen = self._generator()
return self
def _generator(self):
# Put your generator code here
i = 0
while i < self.n:
yield i
i += 1
def next(self):
return next(self._gen)
mi = MyIterable(100)
print len(mi)
for i in mi:
print i,
Ceci est essentiellement une implémentation simple de xrange
, qui retourne un objet dont vous pouvez prendre la longueur, mais ne crée pas de liste explicite.
Vous pouvez utiliser reduce
.
Pour Python 3:
>>> import functools
>>> def gen():
... yield 1
... yield 2
... yield 3
...
>>> functools.reduce(lambda x,y: x + 1, gen(), 0)
Dans Python 2, reduce
est dans l'espace de noms global, l'importation n'est donc pas nécessaire.