190 votes

Longueur de la sortie du générateur

Python fournit une belle méthode pour obtenir la longueur d'un itérable eager, len(x) c'est-à-dire. Mais je n'ai rien trouvé de similaire pour les itérables paresseux représentés par des compréhensions de générateurs et des fonctions. Bien sûr, il n'est pas difficile d'écrire quelque chose comme :

def iterlen(x):
  n = 0
  try:
    while True:
      next(x)
      n += 1
  except StopIteration: pass
  return n

Mais je n'arrive pas à me débarrasser du sentiment que je réimplante un vélo.

(Pendant que je tapais la fonction, une pensée m'a traversé l'esprit : peut-être que cette fonction n'existe pas vraiment, car elle "détruit" son argument. Ce n'est pas un problème pour mon cas, cependant).

P.S. : concernant les premières réponses - oui, quelque chose comme len(list(x)) fonctionnerait aussi, mais cela augmente considérablement l'utilisation de la mémoire.

P.P.S. : revérifié... Ne tenez pas compte du P.S., il semble que j'ai fait une erreur en essayant cela, cela fonctionne bien. Désolé pour le dérangement.

0 votes

Suggérer un changement de titre en Longueur de la sortie du générateur SEULEMENT -- les éléments itérés peuvent être jetés. . Sinon, cette question est confondue avec un autre .

6 votes

reimplementing a bicycle - C'est presque comme réinventer la roue, sauf que c'est un programmeur qui l'a dit.

316voto

Matt Dunham Points 771

Le moyen le plus simple est probablement de sum(1 for _ in gen) où gen est votre générateur.

14 votes

Même si j'aime bien cette solution, l'inconvénient majeur est qu'il n'est pas du tout évident, à la lecture du code, de savoir ce que vous essayez d'obtenir. Si je voyais cette ligne dans le code de quelqu'un d'autre, je me demanderais "pourquoi prend-il la somme ici ? - à moins que j'aie déjà vu ce "hack" auparavant.

26 votes

@CharlesSalvia C'est à cela que servent les commentaires, je pense. Obtenir la longueur d'un générateur est digne d'un commentaire, je dirais.

53 votes

Un autre inconvénient majeur est que cela épuise le générateur juste pour obtenir la longueur, ce qui va généralement à l'encontre de l'objectif premier des générateurs.

43voto

Adam Rosenfield Points 176408

Il n'y en a pas parce que vous ne pouvez pas le faire dans le cas général - que faire si vous avez un générateur infini paresseux ? Par exemple :

def fib():
    a, b = 0, 1
    while True:
        a, b = b, a + b
        yield a

Cette opération ne se termine jamais mais génère les nombres de Fibonacci. Vous pouvez obtenir autant de nombres de Fibonacci que vous le souhaitez en appelant next() .

Si vous avez vraiment besoin de connaître le nombre d'éléments, vous ne pouvez de toute façon pas les parcourir de façon linéaire une seule fois, il vous suffit d'utiliser une structure de données différente, comme une liste ordinaire.

109 votes

Je ne suis pas sûr de croire/accepter l'explication. sum prend un itérable, même si cet itérable peut être infini et donc "vous ne pouvez pas le faire dans le cas général" pas plus que vous ne pouvez faire Len dans le cas général. Peut-être qu'un raisonnement plus probable est que les gens "s'attendent" à ce que len pour être O(1), ce qui n'est pas le cas pour un itérable général ?

14 votes

Les listes régulières consomment plus de mémoire, ce que le PO veut éviter.

0 votes

@Steve Jessop : Si vous avez beaucoup d'objets, les compter est évidemment O(n) en général. Si vous gardez la trace du nombre d'objets tout en les collectant, c'est O(1). Pour de nombreux cas particuliers, vous pourriez être en mesure d'utiliser la nature des objets pour élaborer un meilleur algorithme (par exemple, compter les grains de riz en les pesant). La consommation de mémoire peut être utilisée pour compter les objets s'ils sont alignés en mémoire. Mais pour les générateurs, il n'existe pas de telle méthode en général.

20voto

Mark Points 49079
def count(iter):
    return sum(1 for _ in iter)

Ou mieux encore :

def count(iter):
    try:
        return len(iter)
    except TypeError:
        return sum(1 for _ in iter)

S'il n'est pas itérable, un message d'erreur sera généré. TypeError .

Ou, si vous voulez compter quelque chose de spécifique dans le générateur :

def count(iter, key=None):
    if key:
        if callable(key):
            return sum(bool(key(x)) for x in iter)
        return sum(x == key for x in iter)
    try:
        return len(iter)
    except TypeError:
        return sum(1 for _ in iter)

8voto

Vous pouvez utiliser enumerate() pour parcourir en boucle le flux de données généré, puis renvoyer le dernier nombre -- le nombre d'éléments.

J'ai essayé d'utiliser itertools.count() avec itertools.izip() mais sans succès. C'est la meilleure/la plus courte réponse que j'ai trouvée :

#!/usr/bin/python

import itertools

def func():
    for i in 'yummy beer':
        yield i

def icount(ifunc):
    size = -1 # for the case of an empty iterator
    for size, _ in enumerate(ifunc()):
        pass
    return size + 1

print list(func())
print 'icount', icount(func)

# ['y', 'u', 'm', 'm', 'y', ' ', 'b', 'e', 'e', 'r']
# icount 10

La solution de Kamil Kisiel est bien meilleure :

def count_iterable(i):
    return sum(1 for e in i)

8voto

sebleblanc Points 586

Par définition, seul un sous-ensemble de générateurs reviendra après un certain nombre d'arguments (d'une longueur prédéfinie), et même alors, seul un sous-ensemble de ces générateurs finis a une fin prévisible (l'accès au générateur peut avoir des effets secondaires qui pourraient arrêter le générateur plus tôt).

Si vous souhaitez implémenter des méthodes de longueur pour votre générateur, vous devez d'abord définir ce que vous considérez comme la "longueur" (est-ce le nombre total d'éléments ? le nombre d'éléments restants ?), puis envelopper votre générateur dans une classe. Voici un exemple :

class MyFib(object):
    """
    A class iterator that iterates through values of the
    Fibonacci sequence, until, optionally, a maximum length is reached.
    """

    def __init__(self, length):
        self._length = length
        self._i = 0

     def __iter__(self):
        a, b = 0, 1
        while not self._length or self._i < self._length:
            a, b = b, a + b
            self._i += 1
            yield a

    def __len__(self):
        "This method returns the total number of elements"
        if self._length:
            return self._length
        else:
            raise NotImplementedError("Infinite sequence has no length")
            # or simply return None / 0 depending
            # on implementation

Voici comment l'utiliser :

In [151]: mf = MyFib(20)

In [152]: len(mf)
Out[152]: 20

In [153]: l = [n for n in mf]

In [154]: len(l)
Out[154]: 20

In [155]: l
Out[155]: 
[1,
 1,
 2,
...
6765]

In [156]: mf0 = MyFib(0)

In [157]: len(mf0)
---------------------------------------------------------------------------
NotImplementedError                       Traceback (most recent call last)
<ipython-input-157-2e89b32ad3e4> in <module>()
----> 1 len(mf0)

/tmp/ipython_edit_TWcV1I.py in __len__(self)
     22             return self._length
     23         else:
---> 24             raise NotImplementedError
     25             # or simply return None / 0 depending
     26             # on implementation

NotImplementedError: 

In [158]: g = iter(mf0)

In [159]: l0 = [g.next(), g.next(), g.next()]

In [160]: l0
Out[160]: [1, 1, 2]

0 votes

Il s'agit d'une solution pour mettre en œuvre un itérateur/générateur qui peut fournir une longueur à l'élément de données de l'utilisateur. len() fonction. Vous pouvez dériver votre générateur de cette classe en implémentant votre propre fonction __iter__ et, si nécessaire, votre propre méthode __init__ y __len__ méthode. Ce modèle pourrait être utile, par exemple pour un objet de type ORM, où vous exécutez une requête SQL, puis récupérez les résultats ligne par ligne en utilisant un curseur (via l'itérateur), et la méthode __len__ obtient le compte à partir de la requête SQL actuelle.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X