Maintenant, vous êtes prêts à une discussion sur les
                       générateurs.
            
          
         Exemple 17.17. plural6.py
import re
def rules(language):                                                                 
    for line in file('rules.%s' % language):                                         
        pattern, search, replace = line.split()                                      
        yield lambda word: re.search(pattern, word) and re.sub(search, replace, word)
def plural(noun, language='en'):      
    for applyRule in rules(language): 
        result = applyRule(noun)      
        if result: return result      
 
         Nous utilisons ici une technique appelée un générateur, que je ne
                  vais même pas tenter d'expliquer avant de vous montrer un exemple plus
                  simple.
         
         Exemple 17.18. Présentation des générateurs
>>> def make_counter(x):
...     print 'entering make_counter'
...     while 1:
...         yield x               
...         print 'incrementing x'
...         x = x + 1
...     
>>> counter = make_counter(2) 
>>> counter                   
<generator object at 0x001C9C10>
>>> counter.next()            
entering make_counter
2
>>> counter.next()            
incrementing x
3
>>> counter.next()            
incrementing x
4
               
                  
                       
                      | 
                     La présence du mot-clé yield dans
                                    make_counter signale qu'il ne s'agit pas
                                    d'une fonction ordinaire. C'est un genre de fonction spécial qui
                                    génère des valeurs une par une. Vous pouvez considérer cela comme
                                    une fonction qui reprend sont activité là où elle l'a laissée.
                                    L'appeler retourne un générateur qui peut être utilisée pour
                                    générer des valeurs successives de x.
                      | 
                  
                  
                       
                      | 
                     Pour créer une instance du générateur
                                    make_counter, il suffit de l'appeler comme
                                    toute autre fonction. Notez que cela n'éxécute pas le code de la
                                    fonction, cela se voit au fait que la première ligne de
                                    make_counter est une instruction
                                    print, mais que rien n'est encore
                                    affiché.
                      | 
                  
                  
                       
                      | 
                     La fonction make_counter retourne un
                                    objet générateur.
                      | 
                  
                  
                       
                      | 
                     La première fois que vous appelez la méthode
                                    next() de l'objet générateur, elle exécute le
                                    code de make_counter jusqu'à la première
                                    instruction yield, puis retourne la valeur
                                    produite par yield. Dans ce cas, il s'agit de
                                    2, puisque nous avons créé le générateur par
                                    l'appel make_counter(2).
                      | 
                  
                  
                       
                      | 
                     À chaque appel successif à next(),
                                    l'objet générateur reprend l'exécution au point où il
                                          l'avait laissé et continue jusqu'à l'instruction
                                    yield suivante. Les lignes de code attendant
                                    d'être exécutées sont l'instruction print qui
                                    affiche incrementing x, puis l'instruction
                                    x = x + 1 qui incrémente la variable. Ensuite,
                                    on boucle le while et on revient à
                                    l'instruction yield x, ce qui retourne la
                                    valeur actuelle de x (maintenant égale à
                                    3).
                      | 
                  
                  
                       
                      | 
                     La seconde fois que nous appelons
                                    counter.next(), nous faisons à nouveau la
                                    même chose, mais cette fois x vaut
                                    4. Et cela continue de la même manière. Comme
                                    make_counter définit une boucle infinie, nous
                                    pourrions théoriquement continuer pour l'éternité, le générateur
                                    continuerait d'incrémenter x et de produire sa
                                    valeur. Mais nous allons examiner un exemple plus productif de
                                    l'utilisation des générateurs.
                      | 
                  
               
             
          
         Exemple 17.19. Utilisation des générateurs à la place de la récursion
def fibonacci(max):
    a, b = 0, 1       
    while a < max:
        yield a       
        a, b = b, a+b 
               
                  
                       
                      | 
                     La suite de Fibonacci est une séquence de nombres dans
                                    laquelle chaque nombre est la somme des deux nombres qui le
                                    précèdent. Elle commence par 0 et
                                    1, augmente doucement au début, puis de plus
                                    en plus vite. Pour commencer la séquence, nous utilisons deux
                                    variables : a commence à 0
                                    et b à 1.
                      | 
                  
                  
                       
                      | 
                     a est le nombre en cours dans la
                                    séquence, donc nous le produisons par
                                    yield.
                      | 
                  
                  
                       
                      | 
                     b est le nombre suivant dans la séquence,
                                    nous l'assignons donc à a, mais nous calculons
                                    également la prochaine valeur (a+b) et
                                    l'assignons à b pour l'utiliser au prochain
                                    appel. Notez que les assignements sont faits en parallèle, si
                                    a vaut 3 et
                                    b vaut 5, alors a,
                                       b = b, a+b mettra a à
                                    5 (la valeur précédente de
                                    b) et b à
                                    8 (la somme des valeurs précédentes de
                                    a et b).
                      | 
                  
               
             
          
         Donc, nous obtenons une fonction qui génère les nombres de
                  Fibonacci. Bien sûr, vous pourriez le faire par récursion, mais cette
                  manière est beaucoup plus simple à lire. De plus, elle fonctionne bien
                  avec une boucle for.
         
         Exemple 17.20. Les générateurs dans des boucles for
>>> for n in fibonacci(1000): 
...     print n,              
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987
               
                  
                       
                      | 
                     Nous pouvons utiliser un générateur comme
                                    fibonacci dans une boucle
                                    for directement. La boucle
                                    for crée l'objet générateur et appelle
                                    successivement la méthode next() pour obtenir
                                    des valeurs à assigner à la variable d'index de boucle
                                    (n).
                      | 
                  
                  
                       
                      | 
                     À chaque parcours de la boucle for loop,
                                    n obtient une nouvelle valeur de l'instruction
                                    yield de fibonacci et tout
                                    ce que nous faisons est de l'afficher. Une fois que
                                    fibonacci n'a plus de valeur à retourner
                                    (a est plus grand que max,
                                    dans ce cas que 1000), alors la boucle
                                    for s'achève simplement.
                      | 
                  
               
             
          
         Revenons maintenant à notre fonction plural
                  et voyons l'usage que nous faisons de tout cela.
         
         Exemple 17.21. Les générateurs pour produire des fonctions dynamiques
def rules(language):                                                                 
    for line in file('rules.%s' % language):                                          
        pattern, search, replace = line.split()                                       
        yield lambda word: re.search(pattern, word) and re.sub(search, replace, word) 
def plural(noun, language='en'):      
    for applyRule in rules(language):  
        result = applyRule(noun)      
        if result: return result      
               
                  
                       
                      | 
                     for line in file(...) est un idiome
                                    habituel pour lire le contenu d'un fichier ligne par ligne. Cela
                                    fonctionne parce que file renvoit
                                          en fait un générateur dont la méthode
                                    next() retourne la ligne suivante du fichier.
                                    Personnellement, je trouve ça absolument génial.
                      | 
                  
                  
                       
                      | 
                     Rien de magique ici. Rappelez-vous que les lignes du fichier
                                    de règles contiennent trois valeurs séparées par des espaces,
                                    line.split() retourne donc un tuple de trois
                                    valeurs qui sont assignées à trois variables locales.
                      | 
                  
                  
                       
                      | 
                     Ici, nous utilisons yield. Qu'est-ce
                                    que nous produisons ? Une fonction construite dynamiquement avec
                                    lambda, qui est en fait une fermeture (elle
                                    utilise les variables locales pattern,
                                    search et replace comme
                                    constantes). Autrement dit, rules est un
                                    générateur de fonctions de règles.
                      | 
                  
                  
                       
                      | 
                     Comme rules est un générateur, nous
                                    pouvons l'utiliser directement dans une boucle
                                    for. À la première itération à travers la
                                    boucle, nous appelons la fonction rules, qui
                                    ouvre le fichier de règles, en lit la première ligne, construit
                                    dynamiquement une fonction de recherche et de transformation pour
                                    la première règle définie dans le fichier et produit la fonction
                                    construite dynamiquement. À la seconde itération, nous reprenons
                                    rules au point où nous l'avons laissé (c'est
                                    à dire au milieu de la boucle for line in
                                       file(...)), qui lit la seconde ligne, construit
                                    dynamiquement une nouvelle fonction de recherche et de
                                    transformation pour la seconde règle et la produit. Et ainsi de
                                    suite.
                      | 
                  
               
             
          
         Qu'avons nous gagné par rapport à l'étape 5 ? À l'étape 5, nous lisions le
                  fichier de règles entièrement pour construire une liste de toutes les
                  règles avant même d'essayer la première. Maintenant, grâce aux
                  générateurs, nous pouvons faire tout cela de manière paresseuse : nous
                  lisons la première règle et testons si elle s'applique, et si c'est le
                  cas nous ne lisons pas l'ensemble du fichier ni ne créons d'autre
                  fonctions.