2 votes

Existe-t-il un moyen de copier un générateur arbitraire en Python ?

Parmi les caractéristiques les plus connues de la programmation fonctionnelle figurent l'évaluation paresseuse et les listes infinies. En Python, ces caractéristiques sont généralement mises en œuvre à l'aide de générateurs. Mais l'un des préceptes de la programmation fonctionnelle est l'immuabilité, et les générateurs ne sont pas immuables. C'est tout le contraire. Chaque fois que l'on appelle next() sur un générateur, il modifie son état interne.

Une solution de contournement possible consisterait à copier un générateur avant d'appeler next() sur celui-ci. Cela fonctionne pour certains générateurs tels que count() . (Peut-être count() n'est pas générateur ?)

from itertools import count

count_gen = count()
count_gen_copy = copy(count_gen)
print(next(count_gen), next(count_gen), next(count_gen))  # => 0 1 2
print(next(count_gen_copy), next(count_gen_copy), next(count_gen_copy))  # => 0 1 2

Mais si je définis mon propre générateur, par exemple, my_count() Je ne peux pas le copier.

def my_count(n=0):
    while True:
        yield n
        n += 1

my_count_gen = my_count()
my_count_gen_copy = copy(my_count_gen)
print(next(my_count_gen), next(my_count_gen), next(my_count_gen))
print(next(my_count_gen_copy), next(my_count_gen_copy), next(my_count_gen_copy))

J'obtiens un message d'erreur lorsque j'essaie d'exécuter copy(my_count_gen) : TypeError: can't pickle generator objects .

Existe-t-il un moyen de contourner ce problème ou une autre approche ?

Une autre façon de poser la question est peut-être de se demander ce qu'est la copy() copie quand il copie copy_gen ?

Merci.

P.S. Si j'utilise __iter__() plutôt que copy() , le __iter__() se comporte comme l'original.

my_count_gen = my_count()
my_count_gen_i = my_count_gen.__iter__()
print(next(my_count_gen), next(my_count_gen), next(my_count_gen))  # => 0 1 2
print(next(my_count_gen_i), next(my_count_gen_i), next(my_count_gen_i))  # => 3 4 5

2voto

bj0 Points 825

Tandis que copy n'a pas de sens sur un générateur, vous pouvez effectivement "copier" un itérateur afin de pouvoir l'itérer plusieurs fois. Le moyen le plus simple est d'utiliser tee du module itertools.

def my_count(n=0):
    while True:
        yield n
        n += 1

a, b, c = itertools.tee(my_count(), 3)

# now use a, b, c ...

Cela utilise la mémoire pour mettre en cache les résultats de l'itérateur et les transmettre.

2voto

user2357112 Points 37737

Il n'existe aucun moyen de copier des générateurs arbitraires en Python. L'opération n'a tout simplement pas de sens. Un générateur peut dépendre de toutes sortes d'autres ressources non copiables, comme des handles de fichiers, des connexions à des bases de données, des verrous, des processus de travail, etc. Si un générateur détient un verrou et que vous le copiez, qu'arrivera-t-il au verrou ? Si un générateur se trouve au milieu d'une transaction de base de données et que vous le copiez, qu'arrivera-t-il à la transaction ?

Les choses que vous pensiez être des générateurs copiables ne le sont pas du tout. Ce sont des instances d'autres classes d'itérateurs. Si vous voulez écrire votre propre classe d'itérateur, vous pouvez le faire :

class MyCount:
    def __init__(self, n=0):
        self._n = n
    def __iter__(self):
        return self
    def __next__(self):
        retval = self._n
        self._n += 1
        return retval

Certains itérateurs que vous écrivez de cette manière peuvent même être raisonnablement copiables. D'autres, copy.copy fera quelque chose de complètement déraisonnable et inutile.

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