Python >> Tutoriel Python >  >> Python

Yield Keyword en Python - Un guide illustré simple

Introduction au rendement En Python

Lors de l'utilisation d'une fonction, nous utilisons généralement le return mot clé pour renvoyer une valeur calculée par la fonction. De même, le yield Le mot-clé renvoie également une valeur d'une fonction, mais il maintient également l'état des variables locales à l'intérieur de la fonction et lorsque la fonction est réutilisée dans le programme, l'exécution de la fonction commence à partir de l'état du yield instruction qui a été exécutée dans l'appel de fonction précédent.

Exemple :

def counter():
    x = 1
    while x <= 5:
        yield x
        x += 1

for y in counter():
    print(y)

Sortie :

1
2
3
4
5

Pour comprendre l'utilisation du mot-clé de rendement, vous devez comprendre ce que sont :

  • Itérables
  • Générateurs

Parlons donc des générateurs et des itérables avant de plonger dans le yield mot-clé.

Itérables

Un itérable est un objet en Python à partir duquel nous pouvons obtenir un itérateur. Par exemple, lorsqu'une liste est créée, tous ses éléments peuvent être itérés un par un. Ainsi, la lecture des éléments de la liste un par un est appelée itération alors que la liste est itérable. En Python, les chaînes, les listes, les ensembles, les tuples et les dictionnaires sont des conteneurs itérables à partir desquels nous pouvons obtenir un itérateur.

Exemple :

name = "FINXTER"
li = [1,2,3]
tup = (4,5,6)
s = {"A","B","C"}
d = {"a":100,"b":200,"c":300}

print("\nIterating over String:")
for x in name:
  print(x, end=", ")
print("\nIterating over list:")
for x in li:
  print(x, end=" ")
print("\nIterating over tuple:")
for x in tup:
  print(x, end=" ")
print("\nIterating over set:")
for x in s:
  print(x, end=" ")
print("\nIterating over dictionary:")
for x in d:
  print(d[x], end=" ")

Sortie :

Iterating over String:
F, I, N, X, T, E, R, 
Iterating over list:
1 2 3 
Iterating over tuple:
4 5 6 
Iterating over set:
A C B 
Iterating over dictionary:
100 200 300

Nous savons donc ce qu'est un objet itérable. Mais qu'est-ce qu'un itérateur ?

Itérateur

En termes simples, un itérateur est un objet sur lequel on peut itérer. Les itérateurs sont implémentés à l'aide de boucles.

Les itérateurs implémentent les méthodes suivantes, appelées protocoles d'itérateur :

  • __iter__() :renvoie l'objet itérateur.
  • __suivant__()  :nous permet d'effectuer des opérations et renvoie l'élément suivant dans la séquence.

Jetons un coup d'œil au programme suivant sur la façon dont nous pouvons parcourir un itérateur en Python en utilisant le protocole d'itérateur.

Exemple : Retourner un itérateur à partir d'une liste (itérable) et imprimer chaque valeur une par une :

li = [1,2,3,4,5]
it = iter(li)

print(next(it))
print(next(it))
print(next(it))
print(next(it))
print(next(it))

Sortie :

1
2
3
4
5

Cela nous amène maintenant à la question :quelle est la différence entre un itérateur et un itérable ?

Voici une ligne pour y répondre :

Par exemple, une liste est un itérable mais ce n'est pas un itérateur. Nous pouvons créer un itérateur à partir d'un objet itérable en utilisant l'objet itérable comme indiqué ci-dessus.

Créer des objets itérateurs

Comme mentionné précédemment, le __iter__() et __next__() les méthodes doivent être implémentées dans un objet/une classe pour en faire un itérateur.

Exemple : Le programme suivant illustre la création d'un itérateur qui renvoie une séquence de nombres à partir de 100 et chaque itération augmentera la valeur de 100.

class IterObj:
  def __iter__(self):
    self.value = 100
    return self

  def __next__(self):
    x = self.value
    self.value += 100
    return x

obj = IterObj()
it = iter(obj)

print(next(it))
print(next(it))
print(next(it))

Sortie :

100
200
300

Le programme ci-dessus continuera à imprimer indéfiniment si vous continuez à utiliser le next() déclarations. Il doit y avoir un moyen d'arrêter l'itération pour continuer indéfiniment. C'est là que le StopIteration déclaration entre en vigueur.

ArrêterItération

Une fois l'itération effectuée un certain nombre de fois, nous pouvons définir une condition de fin qui génère une erreur une fois le nombre d'itérations souhaité dépassé. Cette condition de terminaison est donnée par le StopIteration déclaration.

Exemple :

class IterObj:
  def __iter__(self):
    self.value = 100
    return self

  def __next__(self):
    if self.value <= 500:
      x = self.value
      self.value += 100
      return x
    else:
      raise StopIteration

obj = IterObj()
it = iter(obj)

for a in it:
  print(a)

Sortie :

100
200
300
400
500

Générateurs

En utilisant des itérateurs, nous avons appris que nous devions implémenter __iter__() et __next__() méthodes le long et augmenter StopIteration pour garder une trace du nombre d'itérations. Cela peut être assez long et c'est là que les générateurs viennent à notre rescousse. Toutes les procédures à suivre lors de l'utilisation d'itérateurs sont automatiquement gérées par des générateurs.

Générateurs sont des fonctions simples utilisées pour créer des itérateurs et renvoyer un ensemble itérable d'éléments, une valeur à la fois.

➡ Vous ne pouvez parcourir les générateurs qu'une seule fois. Voyons cela dans un programme.

Exemple 1 : Utiliser un itérateur pour itérer deux fois sur les valeurs.

it = [x for x in range(6)]
print("Iterating over generator")
for i in it:
  print(i, end=", ")
print("\nIterating again!")
for j in it:
  print(j, end=", ")

Sortie :

Iterating over generator
0, 1, 2, 3, 4, 5, 
Iterating again!
0, 1, 2, 3, 4, 5,

Exemple 2 : Utilisation du générateur pour parcourir les valeurs. (Le générateur ne peut être utilisé qu'une seule fois, comme indiqué dans la sortie.)

gen = (x for x in range(6))
print("Iterating over generator")
for i in gen:
  print(i, end=", ")
print("\nTrying to Iterate over the generator again!")
for j in gen:
  print(j, end=", ")

Sortie :

Iterating over generator
0, 1, 2, 3, 4, 5, 
Trying to Iterate over the generator again!

➡ Les générateurs ne stockent pas toutes les valeurs en mémoire, mais génèrent les valeurs à la volée. Dans l'exemple 2 ci-dessus, le générateur calcule et imprime la valeur 0 et l'oublie puis calcule et imprime 1 et ainsi de suite.

Cela nous amène maintenant à notre discussion sur le yield mot-clé.

Le rendement Mot clé

Comme mentionné précédemment, yield est un mot clé similaire au return mot-clé, mais en cas de yield la fonction renvoie un générateur.

Exemple : Ce qui suit utilise une fonction génératrice qui produit 7 nombres entiers aléatoires entre 1 et 99.

from random import randint

def game():
    # returns 6 numbers between 1 and 50
    for i in range(6):
        yield randint(1, 50)

    # returns a 7th number between 51 and 99
    yield randint(51,99)

for random_no in game():
       print("Lucky Number : ", (random_no))

Sortie :

 Lucky Number :  12
 Lucky Number :  12
 Lucky Number :  47
 Lucky Number :  36
 Lucky Number :  28
 Lucky Number :  25
 Lucky Number :  55

Dans le programme ci-dessus la fonction générateur game() génère 6 entiers aléatoires entre 1 et 50 en exécutant le yield instruction une à la fois et génère finalement le 7e nombre aléatoire entre 51 et 99 en exécutant le rendement en dehors de la boucle.

Remarque : Lorsque la fonction est appelée, le code dans le corps de la fonction ne s'exécute pas. Au lieu de cela, le corps de la fonction renvoie simplement l'objet générateur, puis le code continuera là où il s'est arrêté à chaque fois que le for boucle utilise le générateur. Difficile!!! N'est-ce pas? ?

Discutons du flux de travail pour simplifier un peu :

  1. Lorsque le for loop est utilisé pour la première fois, il appelle l'objet générateur créé à partir de la fonction. Il exécute le code dans la fonction depuis le début jusqu'à ce qu'il atteigne yield .
  2. Ensuite, il renvoie la première valeur de la boucle.
  3. Ensuite, chaque appel de fonction suivant exécute une autre itération de la boucle à l'intérieur de la fonction et renvoie la valeur suivante.
  4. Cela continue jusqu'à ce que le générateur soit vide, c'est-à-dire lorsque la fonction s'exécute sans yield déclaration. Cela se produit lorsque la boucle est épuisée ou que le if-else la condition n'est plus remplie.

Éléments à retenir :

  • Étant donné que yield stocke l'état des variables locales, la surcharge de l'allocation de mémoire est contrôlée.
  • Cela garantit également que le flux de contrôle du programme ne recommence pas depuis le début, ce qui permet de gagner du temps.
  • Cependant, l'optimisation du temps et de la mémoire peut rendre le code complexe à appréhender.

Comparaison de l'optimisation du temps et de la mémoire pour les fonctions d'itérateur et les générateurs

Exemple 1 : Le programme ci-dessous calcule le temps et l'utilisation de la mémoire lors de l'utilisation d'une fonction avec un itérateur.

import time
import random
import os
import psutil


mobile_name = ["iPhone 11", "iPhone XR", "iPhone 11 Pro Max"]
colors = ["red","black","grey"]
def mobile_list(ph):
    phones = []
    for i in range(ph):
      phone = {
        'name': random.choice(mobile_name),
        'color': random.choice(colors)
      }
      colors.append(phone)
    return phones



# Calculate time of processing
t1 = time.time()
cars = mobile_list(1000000)
t2 = time.time()
print('Took {} seconds'.format(t2-t1))

# Calculate Memory used
process = psutil.Process(os.getpid())
print('Memory used: ' + str(process.memory_info().rss/1000000))

sortie :

Took 14.238950252532959 seconds
Memory used: 267.157504

Exemple 2 : Le programme suivant utilise un générateur avec l'instruction yield au lieu d'une fonction, puis nous calculons la mémoire et le temps utilisés dans ce cas.

import time
import random
import os
import psutil


mobile_name = ["iPhone 11", "iPhone XR", "iPhone 11 Pro Max"]
colors = ["red","black","grey"]
def mobile_list(ph):
    for i in range(ph):
      phone = {
        'name': random.choice(mobile_name),
        'color': random.choice(colors)
      }
      yield phone


# Calculate time of processing
t1 = time.time()
for car in mobile_list(1000000):
    pass
t2 = time.time()
print('Took {} seconds'.format(t2-t1))

# Calculate Memory used
process = psutil.Process(os.getpid())
print('Memory used: ' + str(process.memory_info().rss/1000000))

Sortie :

Took 7.272227048873901 seconds
Memory used: 15.663104

Les exemples ci-dessus illustrent clairement la supériorité des générateurs et yield mot-clé sur les fonctions normales avec return mot-clé.

Avis de non-responsabilité : Vous devez pip install psutil pour que le code fonctionne dans votre machine. De plus, les valeurs de temps et d'utilisation de la mémoire renvoyées varient en fonction des spécifications de la machine utilisée.

Exercice

Maintenant, pratiquons-nous un peu. Exécutez le code ci-dessous pour découvrir un exemple en temps réel de générateurs et le mot clé yield en Python.

Astuce : En mathématiques, les nombres de Fibonacci, communément notés Fₙ, forment une suite, appelée suite de Fibonacci, telle que chaque nombre est la somme des deux précédents, en partant de 0 et 1. C'est-à-dire, et pour n> 1. (Source :Wikipédia)

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

f = fibo()
print(', '.join(str(next(f)) for _ in range(10)))

retour Mot clé vs rendement Mot clé

Avant de conclure notre discussion, terminons ce que nous avons commencé et discutons de la différence entre le yield et return déclarations en Python.

Conclusion

Dans cet article, nous avons appris :

  • Que sont les itérables ?
  • Que sont les itérateurs ?
  • La différence entre Itérables et Itérateurs.
  • Création d'objets itérateurs.
  • Le StopIteration déclaration.
  • Que sont les générateurs en Python ?
  • Le mot clé de rendement.
  • Comparaison de l'optimisation du temps et de la mémoire pour les fonctions d'itérateur et les générateurs
  • La différence entre return et yield mots-clés.

Voici un petit récapitulatif des concepts que nous avons appris dans cet article; veuillez suivre le diaporama ci-dessous :

Veuillez vous abonner et rester à l'écoute pour d'autres articles intéressants !