Maison  >  Article  >  développement back-end  >  Analyse de l'utilisation du rendement Python

Analyse de l'utilisation du rendement Python

巴扎黑
巴扎黑original
2017-04-05 16:04:151385parcourir

Vous avez peut-être entendu dire qu'une fonction avec rendement est appelée un générateur en Python. Qu'est-ce qu'un générateur ?

Laissons d’abord de côté les générateurs et utilisons un sujet de programmation commun pour démontrer le concept de rendement.

Comment générer la séquence de Fibonacci

La séquence de Fibonacci est une séquence récursive très simple À l'exception du premier et du deuxième nombre, n'importe quel nombre peut être obtenu en additionnant les deux premiers nombres. Utiliser un programme informatique pour générer les N premiers nombres de la séquence de Fibonacci est un problème très simple De nombreux débutants peuvent facilement écrire la fonction suivante :

.  Liste 1. Sortie simple des N premiers nombres de la séquence de Fibonacci

 def fab(max): 
    n, a, b = 0, 0, 1 
    while n < max: 
        print b 
        a, b = b, a + b 
        n = n + 1

En exécutant fab(5), nous pouvons obtenir le résultat suivant :

 >>> fab(5) 
 1 
 1 
 2 
 3 
 5

Il n'y a aucun problème avec le résultat, mais les développeurs expérimentés souligneront que l'utilisation de print pour imprimer des nombres directement dans la fonction fab entraînera une mauvaise réutilisation de la fonction, car la fonction fab renvoie None et les autres fonctions ne peuvent pas obtenir la séquence générée par le fonction.

Pour améliorer la réutilisabilité de la fonction fab, il est préférable de ne pas imprimer directement la séquence, mais de renvoyer une liste. Ce qui suit est la deuxième version réécrite de la fonction fab :

Listing 2. Afficher les N premiers nombres de la deuxième version de la séquence de Fibonacci

 def fab(max): 
    n, a, b = 0, 0, 1 
    L = [] 
    while n < max: 
        L.append(b) 
        a, b = b, a + b 
        n = n + 1 
    return L

Vous pouvez utiliser la méthode suivante pour imprimer la liste renvoyée par la fonction fab :

 >>> for n in fab(5): 
 ...     print n 
 ... 
 1 
 1 
 2 
 3 
 5

La fonction fab réécrite peut répondre aux exigences de réutilisabilité en renvoyant une liste, mais les développeurs plus expérimentés souligneront que la mémoire occupée par cette fonction pendant le fonctionnement augmentera à mesure que le paramètre max augmente. Si vous souhaitez contrôler l'occupation de la mémoire, il est préférable. ne pas utiliser List

Pour enregistrer les résultats intermédiaires, parcourez les objets itérables. Par exemple, dans Python2.x, codez :

Listing 3. Itération à travers des objets itérables

 for i in range(1000): pass

aura pour conséquence de générer une Liste de 1000 éléments, et le code :

 for i in xrange(1000): pass

ne générera pas une liste de 1000 éléments, mais renverra la valeur suivante à chaque itération, occupant très peu d'espace mémoire. Parce que xrange ne renvoie pas une liste, mais un objet itérable.

En utilisant iterable, nous pouvons réécrire la fonction fab dans une classe qui prend en charge iterable. Voici la troisième version de Fab :

Listing 4. La troisième version

 class Fab(object): 

    def __init__(self, max): 
        self.max = max 
        self.n, self.a, self.b = 0, 0, 1 

    def __iter__(self): 
        return self 

    def next(self): 
        if self.n < self.max: 
            r = self.b 
            self.a, self.b = self.b, self.a + self.b 
            self.n = self.n + 1 
            return r 
        raise StopIteration()

La classe Fab renvoie continuellement le numéro suivant dans la séquence via next(), et l'utilisation de la mémoire est toujours constante :

 >>> for n in fab(5): 
 ...     print n 
 ... 
 1 
 1 
 2 
 3 
 5

Cependant, le code de cette version réécrite en utilisant class est bien moins concis que la première version de la fonction fab. Si l'on veut conserver la simplicité de la première version de la fonction fab tout en obtenant l'effet d'itérable, le rendement s'avère pratique :

Listing 5. Quatrième version utilisant le rendement

 def fab(max): 
    n, a, b = 0, 0, 1 
    while n < max: 
        yield b 
        # print b 
        a, b = b, a + b 
        n = n + 1 

&#39;&#39;&#39;

Par rapport à la première version, la quatrième version de fab a uniquement modifié print b en rendement b, ce qui a permis d'obtenir l'effet itérable tout en conservant la simplicité.

L'appel de la quatrième version de fab est exactement le même que celui de la deuxième version de fab :

 >>> for n in fab(5): 
 ...     print n 
 ... 
 1 
 1 
 2 
 3 
 5

En termes simples, la fonction de rendement est de transformer une fonction en générateur. La fonction avec rendement n'est plus une fonction ordinaire. L'interpréteur Python la traitera comme un générateur. L'appel de fab(5) n'exécutera pas la fonction fab. , il renvoie un objet itérable ! Lorsque la boucle for est exécutée, chaque boucle exécutera le code à l'intérieur de la fonction fab. Lorsqu'elle atteint le rendement b, la fonction fab renvoie une valeur d'itération. Lors de l'itération suivante, le code continue de s'exécuter à partir de l'instruction suivante de rendement b. et la fonction La variable locale a exactement la même apparence qu'avant la dernière interruption, donc la fonction continue son exécution jusqu'à ce que rendement soit à nouveau rencontré.

Vous pouvez également appeler manuellement la méthode next() de fab(5) (car fab(5) est un objet générateur, qui a une méthode next()), afin que nous puissions voir plus clairement le processus d'exécution de fab :

Liste 6. Processus d'exécution

 >>> f = fab(5) 
 >>> f.next() 
 1 
 >>> f.next() 
 1 
 >>> f.next() 
 2 
 >>> f.next() 
 3 
 >>> f.next() 
 5 
 >>> f.next() 
 Traceback (most recent call last): 
  File "<stdin>", line 1, in <module> 
 StopIteration

Lorsque l'exécution de la fonction se termine, le générateur lève automatiquement une exception StopIteration, indiquant que l'itération est terminée. Dans la boucle for, il n'est pas nécessaire de gérer l'exception StopIteration et la boucle se terminera normalement.

​Nous pouvons tirer les conclusions suivantes :

Une fonction avec rendement est un générateur. Elle est différente d'une fonction ordinaire. La génération d'un générateur ressemble à un appel de fonction, mais n'exécutera aucun code de fonction tant que next() ne sera pas appelé dessus (next( sera appelé automatiquement dans un for). boucle) )) avant de commencer l’exécution. Bien que le flux d'exécution soit toujours exécuté en fonction du flux de la fonction, chaque fois qu'une instruction de rendement est exécutée, elle sera interrompue et une valeur d'itération sera renvoyée. La prochaine exécution continuera à partir de l'instruction de rendement suivante. Il semble qu'une fonction soit interrompue plusieurs fois par rendement lors d'une exécution normale, et chaque interruption renvoie la valeur d'itération actuelle via rendement.

Les avantages de rendement sont évidents. En réécrivant une fonction en tant que générateur, vous obtenez la possibilité d'itérer. Par rapport à l'utilisation d'une instance d'une classe pour enregistrer l'état afin de calculer la valeur next(), le code est non seulement concis, mais également. le processus d'exécution est extrêmement clair.

  如何判断一个函数是否是一个特殊的 generator 函数?可以利用 isgeneratorfunction 判断:

  清单 7. 使用 isgeneratorfunction 判断

 >>> from inspect import isgeneratorfunction 
 >>> isgeneratorfunction(fab) 
 True

  要注意区分 fab 和 fab(5),fab 是一个 generator function,而 fab(5) 是调用 fab 返回的一个 generator,好比类的定义和类的实例的区别:

  清单 8. 类的定义和类的实例

 >>> import types 
 >>> isinstance(fab, types.GeneratorType) 
 False 
 >>> isinstance(fab(5), types.GeneratorType) 
 True

  fab 是无法迭代的,而 fab(5) 是可迭代的:

 >>> from collections import Iterable 
 >>> isinstance(fab, Iterable) 
 False 
 >>> isinstance(fab(5), Iterable) 
 True

  每次调用 fab 函数都会生成一个新的 generator 实例,各实例互不影响:

 >>> f1 = fab(3) 
 >>> f2 = fab(5) 
 >>> print &#39;f1:&#39;, f1.next() 
 f1: 1 
 >>> print &#39;f2:&#39;, f2.next() 
 f2: 1 
 >>> print &#39;f1:&#39;, f1.next() 
 f1: 1 
 >>> print &#39;f2:&#39;, f2.next() 
 f2: 1 
 >>> print &#39;f1:&#39;, f1.next() 
 f1: 2 
 >>> print &#39;f2:&#39;, f2.next() 
 f2: 2 
 >>> print &#39;f2:&#39;, f2.next() 
 f2: 3 
 >>> print &#39;f2:&#39;, f2.next() 
 f2: 5

  return 的作用

  在一个 generator function 中,如果没有 return,则默认执行至函数完毕,如果在执行过程中 return,则直接抛出 StopIteration 终止迭代。

  另一个例子

  另一个 yield 的例子来源于文件读取。如果直接对文件对象调用 read() 方法,会导致不可预测的内存占用。好的方法是利用固定长度的缓冲区来不断读取文件内容。通过 yield,我们不再需要编写读文件的迭代类,就可以轻松实现文件读取:

  清单 9. 另一个 yield 的例子

 def read_file(fpath): 
    BLOCK_SIZE = 1024 
    with open(fpath, &#39;rb&#39;) as f: 
        while True: 
            block = f.read(BLOCK_SIZE) 
            if block: 
                yield block 
            else: 
                return

  以上仅仅简单介绍了 yield 的基本概念和用法,yield 在 Python 3 中还有更强大的用法,我们会在后续文章中讨论。

  注:本文的代码均在 Python 2.7 中调试通过

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn