Fabrication industrielle
Internet des objets industriel | Matériaux industriels | Entretien et réparation d'équipement | Programmation industrielle |
home  MfgRobots >> Fabrication industrielle >  >> Industrial programming >> Python

Générateurs Python

Générateurs Python

Dans ce didacticiel, vous apprendrez à créer facilement des itérations à l'aide de générateurs Python, en quoi cela diffère des itérateurs et des fonctions normales, et pourquoi vous devriez l'utiliser.

Vidéo :Générateurs Python

Générateurs en Python

Il y a beaucoup de travail dans la construction d'un itérateur en Python. Nous devons implémenter une classe avec __iter__() et __next__() méthode, garder une trace des états internes et augmenter StopIteration lorsqu'il n'y a aucune valeur à renvoyer.

C'est à la fois long et contre-intuitif. Le générateur vient à la rescousse dans de telles situations.

Les générateurs Python sont un moyen simple de créer des itérateurs. Tout le travail que nous avons mentionné ci-dessus est automatiquement géré par des générateurs en Python.

En termes simples, un générateur est une fonction qui renvoie un objet (itérateur) sur lequel nous pouvons itérer (une valeur à la fois).

Créer des générateurs en Python

Il est assez simple de créer un générateur en Python. C'est aussi simple que de définir une fonction normale, mais avec un yield déclaration au lieu d'un return déclaration.

Si une fonction contient au moins un yield déclaration (elle peut contenir d'autres yield ou return instructions), elle devient une fonction génératrice. Les deux yield et return renverra une valeur d'une fonction.

La différence est que si un return l'instruction termine entièrement une fonction, yield interrompt la fonction en sauvegardant tous ses états et continue ensuite à partir de là lors d'appels successifs.

Différences entre la fonction Générateur et la fonction Normale

Voici en quoi une fonction génératrice diffère d'une fonction normale.

  • La fonction générateur contient un ou plusieurs yield déclarations.
  • Lorsqu'il est appelé, il renvoie un objet (itérateur) mais ne démarre pas l'exécution immédiatement.
  • Méthodes telles que __iter__() et __next__() sont mis en œuvre automatiquement. Nous pouvons donc parcourir les éléments en utilisant next() .
  • Une fois que la fonction cède, la fonction est mise en pause et le contrôle est transféré à l'appelant.
  • Les variables locales et leurs états sont mémorisés entre les appels successifs.
  • Enfin, lorsque la fonction se termine, StopIteration est déclenché automatiquement lors d'appels ultérieurs.

Voici un exemple pour illustrer tous les points énoncés ci-dessus. Nous avons une fonction génératrice nommée my_gen() avec plusieurs yield déclarations.

# A simple generator function
def my_gen():
    n = 1
    print('This is printed first')
    # Generator function contains yield statements
    yield n

    n += 1
    print('This is printed second')
    yield n

    n += 1
    print('This is printed at last')
    yield n

Une exécution interactive dans l'interpréteur est donnée ci-dessous. Exécutez-les dans le shell Python pour voir la sortie.

>>> # It returns an object but does not start execution immediately.
>>> a = my_gen()

>>> # We can iterate through the items using next().
>>> next(a)
This is printed first
1
>>> # Once the function yields, the function is paused and the control is transferred to the caller.

>>> # Local variables and theirs states are remembered between successive calls.
>>> next(a)
This is printed second
2

>>> next(a)
This is printed at last
3

>>> # Finally, when the function terminates, StopIteration is raised automatically on further calls.
>>> next(a)
Traceback (most recent call last):
...
StopIteration
>>> next(a)
Traceback (most recent call last):
...
StopIteration

Une chose intéressante à noter dans l'exemple ci-dessus est que la valeur de la variable n est mémorisé entre chaque appel.

Contrairement aux fonctions normales, les variables locales ne sont pas détruites lorsque la fonction produit. De plus, l'objet générateur ne peut être itéré qu'une seule fois.

Pour redémarrer le processus, nous devons créer un autre objet générateur en utilisant quelque chose comme a = my_gen() .

Une dernière chose à noter est que nous pouvons utiliser directement des générateurs avec des boucles for.

C'est parce qu'un for loop prend un itérateur et le parcourt en utilisant next() fonction. Il se termine automatiquement lorsque StopIteration est relevé. Vérifiez ici pour savoir comment une boucle for est réellement implémentée en Python.

# A simple generator function
def my_gen():
    n = 1
    print('This is printed first')
    # Generator function contains yield statements
    yield n

    n += 1
    print('This is printed second')
    yield n

    n += 1
    print('This is printed at last')
    yield n


# Using for loop
for item in my_gen():
    print(item)

Lorsque vous exécutez le programme, la sortie sera :

This is printed first
1
This is printed second
2
This is printed at last
3

Générateurs Python avec une boucle

L'exemple ci-dessus est moins utile et nous l'avons étudié juste pour avoir une idée de ce qui se passait en arrière-plan.

Normalement, les fonctions de générateur sont implémentées avec une boucle ayant une condition de terminaison appropriée.

Prenons un exemple de générateur qui inverse une chaîne.

def rev_str(my_str):
    length = len(my_str)
    for i in range(length - 1, -1, -1):
        yield my_str[i]


# For loop to reverse the string
for char in rev_str("hello"):
    print(char)

Sortie

o
l
l
e
h

Dans cet exemple, nous avons utilisé le range() pour obtenir l'index dans l'ordre inverse à l'aide de la boucle for.

Remarque  :Cette fonction de générateur fonctionne non seulement avec des chaînes, mais également avec d'autres types d'itérables comme la liste, le tuple, etc.

Expression du générateur Python

Des générateurs simples peuvent être facilement créés à la volée à l'aide d'expressions de générateur. Cela facilite la construction de générateurs.

Semblables aux fonctions lambda qui créent des fonctions anonymes, les expressions de générateur créent des fonctions de générateur anonymes.

La syntaxe de l'expression du générateur est similaire à celle d'une compréhension de liste en Python. Mais les crochets sont remplacés par des parenthèses rondes.

La principale différence entre une compréhension de liste et une expression génératrice est qu'une compréhension de liste produit la liste entière tandis que l'expression génératrice produit un élément à la fois.

Ils ont une exécution paresseuse (produisant des éléments uniquement sur demande). Pour cette raison, une expression génératrice est beaucoup plus économe en mémoire qu'une compréhension de liste équivalente.

# Initialize the list
my_list = [1, 3, 6, 10]

# square each term using list comprehension
list_ = [x**2 for x in my_list]

# same thing can be done using a generator expression
# generator expressions are surrounded by parenthesis ()
generator = (x**2 for x in my_list)

print(list_)
print(generator)

Sortie

[1, 9, 36, 100]
<generator object <genexpr> at 0x7f5d4eb4bf50>

Nous pouvons voir ci-dessus que l'expression du générateur n'a pas produit le résultat requis immédiatement. Au lieu de cela, il a renvoyé un objet générateur, qui produit des éléments uniquement à la demande.

Voici comment nous pouvons commencer à obtenir des éléments du générateur :

# Initialize the list
my_list = [1, 3, 6, 10]

a = (x**2 for x in my_list)
print(next(a))

print(next(a))

print(next(a))

print(next(a))

next(a)

Lorsque nous exécutons le programme ci-dessus, nous obtenons la sortie suivante :

1
9
36
100
Traceback (most recent call last):
  File "<string>", line 15, in <module>
StopIteration

Les expressions de générateur peuvent être utilisées comme arguments de fonction. Lorsqu'elles sont utilisées de cette manière, les parenthèses rondes peuvent être supprimées.

>>> sum(x**2 for x in my_list)
146

>>> max(x**2 for x in my_list)
100

Utilisation des générateurs Python

Plusieurs raisons font des générateurs une implémentation puissante.

1. Facile à mettre en œuvre

Les générateurs peuvent être implémentés de manière claire et concise par rapport à leur homologue de classe d'itérateur. Voici un exemple pour implémenter une séquence de puissance de 2 en utilisant une classe d'itérateur.

class PowTwo:
    def __init__(self, max=0):
        self.n = 0
        self.max = max

    def __iter__(self):
        return self

    def __next__(self):
        if self.n > self.max:
            raise StopIteration

        result = 2 ** self.n
        self.n += 1
        return result

Le programme ci-dessus était long et déroutant. Maintenant, faisons la même chose en utilisant une fonction de générateur.

def PowTwoGen(max=0):
    n = 0
    while n < max:
        yield 2 ** n
        n += 1

Étant donné que les générateurs gardent automatiquement une trace des détails, la mise en œuvre était concise et beaucoup plus propre.

2. Mémoire efficace

Une fonction normale pour renvoyer une séquence créera la séquence entière en mémoire avant de renvoyer le résultat. C'est exagéré, si le nombre d'éléments dans la séquence est très grand.

L'implémentation du générateur de telles séquences est compatible avec la mémoire et est préférable car elle ne produit qu'un seul élément à la fois.

3. Représenter le flux infini

Les générateurs sont d'excellents médiums pour représenter un flux infini de données. Les flux infinis ne peuvent pas être stockés en mémoire, et puisque les générateurs ne produisent qu'un seul élément à la fois, ils peuvent représenter un flux infini de données.

La fonction génératrice suivante peut générer tous les nombres pairs (du moins en théorie).

def all_even():
    n = 0
    while True:
        yield n
        n += 2

4. Générateurs de pipeline

Plusieurs générateurs peuvent être utilisés pour canaliser une série d'opérations. Ceci est mieux illustré à l'aide d'un exemple.

Supposons que nous ayons un générateur qui produit les nombres de la série de Fibonacci. Et nous avons un autre générateur pour élever les nombres au carré.

Si nous voulons connaître la somme des carrés des nombres de la série de Fibonacci, nous pouvons le faire de la manière suivante en canalisant ensemble la sortie des fonctions génératrices.

def fibonacci_numbers(nums):
    x, y = 0, 1
    for _ in range(nums):
        x, y = y, x+y
        yield x

def square(nums):
    for num in nums:
        yield num**2

print(sum(square(fibonacci_numbers(10))))

Sortie

4895

Ce pipeline est efficace et facile à lire (et oui, beaucoup plus cool !).


Python

  1. Opérateurs Python
  2. Arguments de la fonction Python
  3. Dictionnaire Python
  4. Générateurs Python
  5. Fermetures Python
  6. Décorateurs Python
  7. Fonctions Python Lambda avec EXEMPLES
  8. Fonction Python abs() :Exemples de valeurs absolues
  9. Fonction Python round() avec EXEMPLES