Existe-t-il une expression pour un générateur infini?

119

Existe-t-il une expression génératrice simple qui peut produire des éléments infinis?

C'est une question purement théorique. Pas besoin de réponse "pratique" ici :)


Par exemple, il est facile de créer un générateur fini:

my_gen = (0 for i in xrange(42))

Cependant, pour en créer un infini, j'ai besoin de "polluer" mon espace de noms avec une fonction bidon:

def _my_gen():
    while True:
        yield 0
my_gen = _my_gen()

Faire des choses dans un fichier séparé et importplus tard ne compte pas.


Je sais aussi que cela itertools.repeatfait exactement cela. Je suis curieux de savoir s'il existe une solution à une seule ligne sans cela.

hugomg
la source
9
Vous n'avez pas vraiment besoin de polluer votre espace de noms ... nommez simplement la fonction my_gen, puis faites my_gen = my_gen().
6502
2
vous pouvez également utiliser del _my_gensi vous ne voulez pas confondre les deux
John La Rooy

Réponses:

133
for x in iter(int, 1): pass
  • Deux arguments iter= appelable sans argument + valeur sentinelle
  • int() revient toujours 0

Par conséquent, iter(int, 1)est un itérateur infini. Il existe évidemment un grand nombre de variations sur ce thème particulier (surtout une fois que vous l'avez ajouté lambdaau mix). Une variante particulièrement intéressante est iter(f, object())que l'utilisation d'un objet fraîchement créé comme valeur sentinelle garantit presque un itérateur infini quel que soit l'appelable utilisé comme premier argument.

ncoghlan
la source
3
manière très intéressante à utiliser iteravec des propriétés intdont nous oublions souvent.
Senthil Kumaran
3
vous pouvez utiliser cette recette magique pour simuler itertools.count:count = lambda start=0, step=1: (start + i*step for i, _ in enumerate(iter(int, 1)))
Coffee_Table
1
Juste pour expliquer ce qui se passe ici: Quand la iter-fonction est appelée avec deux arguments, il se comporte un peu différent que d' habitude: iter(callable, sentinel) -> iterator. L'argument 1, callableest appelé pour chaque itération de l'itérateur, jusqu'à ce qu'il renvoie la valeur de sentinel. Cependant, comme int()nous le reviendrons toujours 0, nous pouvons appeler int()pour toujours et ne jamais atteindre 1. Cela produira en fait une liste infinie de 0's
Olsgaard
217

itertools fournit trois générateurs infinis:

Je n'en connais pas d'autres dans la bibliothèque standard.


Puisque vous avez demandé un monoplace:

__import__("itertools").count()
Katriel
la source
18
Re: repeat (x, times = ∞) - il n'y a pas de symbole pour celui qui se demande - l'omission de l'argument rend la répétition à jamais
Mr_and_Mrs_D
Évalué parce que (alors que la réponse de ncoghlan répond directement à la question du PO) cela est plus généralement applicable.
Huw Walters
C'est bien plus lisible que l' iter(int, 1)incantation. Dommage itertoolsqu'il n'y ait pas de endlessly()méthode dont le seul but est de faire cela; itertools.count()n'est pas tout à fait lisible non plus.
BallpointBen le
19

vous pouvez itérer sur un appelable en retournant une constante toujours différente de la sentinelle de iter ()

g1=iter(lambda:0, 1)
utilisateur237419
la source
8
J'adore et je déteste ça ... J'adore qu'il accomplisse ce que je veux en si peu de personnages, mais je déteste le fait que personne ne puisse jamais le regarder et savoir ce qu'il est censé faire.
ArtOfWarfare
1
connaissant la syntaxe de iter(ici avec une sentinelle supplémentaire) et la syntaxe de lambda(ici sans paramètres passés, juste return 0), le seul endroit à haïr est cet énigmatique g1.
Sławomir Lenart
@ Les hommes de SławomirLenart ne comprennent jamais. c'est juste qu'il était trop petit alors j'ai giclé 1g.
user237419
8

Votre système d'exploitation peut fournir quelque chose qui peut être utilisé comme un générateur infini. Par exemple sur Linux

for i in (0 for x in open('/dev/urandom')):
    print i

évidemment ce n'est pas aussi efficace que

for i in __import__('itertools').repeat(0)
    print i
John La Rooy
la source
11
La solution / dev / urandom dépend du fait que des \ns apparaissent de temps en temps ... Sournois! :)
hugomg
5

Aucun qui n'utilise en interne un autre itérateur infini défini comme une classe / fonction / générateur (pas -expression, une fonction avec yield). Une expression de générateur s'inspire toujours d'un itérable et ne fait que filtrer et mapper ses éléments. Vous ne pouvez pas passer d'éléments finis à des éléments infinis avec seulement mapet filter, dont vous avez besoin while(ou un forqui ne se termine pas, ce qui est exactement ce que nous ne pouvons pas avoir en utilisant uniquement fordes itérateurs finis).

Trivia: PEP 3142 est superficiellement similaire, mais en y regardant de plus près, il semble qu'il nécessite toujours la forclause (donc non (0 while True)pour vous), c'est-à-dire ne fournit qu'un raccourci pour itertools.takewhile.


la source
Comme je le soupçonnais ... Pouvons-nous être sûrs qu'il n'y a pas de générateur infini facilement disponible pour abuser? (Malheureusement, xrange (0,1, -1) ne fonctionne pas ...)
hugomg
2
@missingno: from itertools import repeat, count, cyclecompte probablement comme "facilement disponible" pour la plupart des gens.
ncoghlan
1
Oups, j'ai oublié 2 arguments iter. Les itérateurs infinis sont en fait disponibles en tant que intégrés - voir ma réponse :)
ncoghlan
5

Assez moche et fou (très drôle cependant), mais vous pouvez construire votre propre itérateur à partir d'une expression en utilisant quelques astuces (sans "polluer" votre espace de noms comme requis):

{ print("Hello world") for _ in
    (lambda o: setattr(o, '__iter__', lambda x:x)
            or setattr(o, '__next__', lambda x:True)
            or o)
    (type("EvilIterator", (object,), {}))() } 
Thomas Baruchel
la source
Vous aimez clairement LISP
Faissaloo
1
@Faissaloo En effet ... Vous pouvez trouver une expression encore plus insensée sur une vieille page que j'ai écrite: baruchel.github.io/python/2018/06/20
Thomas Baruchel
2

Vous pourriez peut-être utiliser des décorateurs comme celui-ci par exemple:

def generator(first):
    def wrap(func):
        def seq():
            x = first
            while True:
                yield x
                x = func(x)
        return seq
    return wrap

Utilisation (1):

@generator(0)
def blah(x):
    return x + 1

for i in blah():
    print i

Utilisation (2)

for i in generator(0)(lambda x: x + 1)():
    print i

Je pense qu'il pourrait être encore amélioré pour se débarrasser de ces moche (). Cependant cela dépend de la complexité de la séquence que vous souhaitez pouvoir créer. De manière générale, si votre séquence peut être exprimée à l'aide de fonctions, alors toute la complexité et le sucre syntaxique des générateurs peuvent être cachés à l'intérieur d'un décorateur ou d'une fonction de type décorateur.

Julx
la source
9
OP demande un oneliner et vous présentez un décorateur 10 lignes avec triple emboîtement defet fermeture? ;)
2
@delnan Eh bien, mais si vous définissez le décorateur une fois, vous pouvez avoir vos doublures, n'est-ce pas? Si je comprends bien, le but est d'avoir chaque générateur infini supplémentaire implémenté dans une ligne. Et c'est ce qui est présenté ici. Vous pouvez avoir (2^x), vous pouvez avoir (x). Si vous l'améliorez un peu peut-être aussi fibonacci, etc.
julx
Ne répond pas à ma question, mais alors comment ne pas aimer toutes ces fermetures moelleuses? BTW, je suis à peu près sûr que vous pouvez vous débarrasser des parens supplémentaires en seqwrap
supprimant