web-dev-qa-db-fra.com

Comment len ​​(generateur ())

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?

124
Jonathan

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)
58
Jochen Ritzel

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.

208
Rosh Oxymoron

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)
19
unutbu

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])
13
Greg Hewgill

Vous pouvez len(list(generator)) mais vous pourriez probablement améliorer quelque chose si vous avez vraiment l'intention de supprimer les résultats.

7
Ben Jackson

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

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.

4
Ned Batchelder

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.

4
hwiechers