Maison > Article > développement back-end > Explication détaillée du générateur Python dans un article
Cet article vous apporte des connaissances pertinentes sur python, qui introduit principalement les problèmes liés aux générateurs, y compris le concept de générateur, le processus d'exécution des générateurs, les méthodes de rendement et de générateur, etc., comme suit Jetons un coup d'œil, j'espère que sera utile à tout le monde.
Apprentissage recommandé : Tutoriel vidéo Python
Cet article vous apporte des connaissances pertinentes sur Python, qui présente principalement des problèmes liés aux générateurs, y compris le concept de générateurs et les fonctions des générateurs. Jetons un coup d'œil au processus d'exécution , méthodes de rendement et de génération, etc. J'espère que cela sera utile à tout le monde.
Generator (anglais : générateur) est une chose très fascinante et est souvent considérée comme une compétence de programmation avancée en Python. Cependant, je suis toujours heureux de discuter de ce sujet avec les lecteurs ici - même si vous êtes peut-être débutant - car je crois que le but de la lecture de ce tutoriel n'est pas de vous limiter au niveau débutant. Vous devez avoir un cœur indiscipliné - pour le faire. devenez un maître Python. Commençons donc par en apprendre davantage sur les générateurs.
Vous vous souvenez du « itérateur » dans la section précédente ? Les générateurs et les itérateurs ont une certaine relation d'origine. Le générateur doit être itérable. Il est vrai que ce n'est pas seulement un itérateur, mais à part ça, il n'a pas beaucoup d'autres utilisations, on peut donc le comprendre comme un itérateur personnalisé très pratique.
2. Générateur simple
>>> my_generator = (x*x for x in range(4))
Est-ce très similaire à la compréhension de liste ? Observez attentivement, ce n'est pas une liste. Si vous obtenez comme ceci, c'est une liste :
>>> my_list = [x*x for x in range(4)]
>>> dir(my_generator) ['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__iter__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'next', 'send', 'throw']
Pour une observation plus facile, j'ai réorganisé les résultats ci-dessus. Avez-vous découvert les méthodes nécessaires __inter__() et next() dans les itérateurs, ce qui montre qu'il s'agit d'un itérateur. S'il s'agit d'un itérateur, vous pouvez utiliser une boucle for pour lire ses valeurs dans l'ordre
>>> for i in my_generator: ... print i ... 0 1 4 9 >>> for i in my_generator: ... print i ...
Lorsque vous le parcourez pour la première fois, vous lisez et imprimez les valeurs dans my_generator dans l'ordre. lorsque vous le relisez, vous constatez qu’il n’y a aucun résultat. Cette fonctionnalité est également celle des itérateurs.
Pour cette liste, c'est différent :
>>> for i in my_list: ... print i ... 0 1 4 9 >>> for i in my_list: ... print i ... 0 1 4 9
Se pourrait-il que le générateur remplace simplement [] dans l'analyse de la liste par () ? Il s'agit simplement d'une forme d'expression et d'utilisation des générateurs. Suite à la dénomination des compréhensions de liste
, elle peut être appelée « expression analytique du générateur » (ou : dérivation du générateur, expression du générateur).
Les expressions d'analyse du générateur ont de nombreuses utilisations et constituent un bon choix pour remplacer les listes à de nombreux endroits. Surtout lorsqu'il s'agit d'un grand nombre de valeurs, comme mentionné dans la section précédente, les listes occupent plus de mémoire. L'avantage des itérateurs (les générateurs sont des itérateurs) est qu'ils occupent moins de mémoire, il n'est donc pas nécessaire d'instancier le générateur (ou l'itérateur). ) Transformez-le en liste et utilisez-le directement pour montrer les avantages de l'itération. Par exemple :
>>> sum(i*i for i in range(10)) 285
Faites attention à l'opération sum() ci-dessus. Ne pensez pas qu'il manque des parenthèses, c'est juste écrit comme ceci. N'est-ce pas charmant ? Si la liste, vous devez :
>>> sum([i*i for i in range(10)]) 285
Le générateur obtenu par l'analyse du générateur est couvert de quelques détails du générateur, et les zones applicables sont limitées. Ensuite, nous analyserons les composants internes du générateur et acquerrons une compréhension plus approfondie de cet outil magique.
3. Définition et processus d'exécution
yield signifie "production, production" en chinois. En Python, il est utilisé comme mot-clé (vous ne pouvez pas l'utiliser dans les noms de variables, de fonctions et de classes
Ceci) est le symbole du générateur.
>>> def g(): ... yield 0 ... yield 1 ... yield 2 ... >>> g <function g at 0xb71f3b8c>
>>> ge = g() >>> ge <generator object g at 0xb7200edc> >>> type(ge) <type 'generator'>
La valeur de retour de la fonction établie ci-dessus est un objet de type générateur.
>>> dir(ge) ['__class__', '__delattr__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__iter__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'close', 'gi_code', 'gi_frame', 'gi_running', 'next', 'send', 'throw']
J'ai vu __iter__() et next() ici, indiquant qu'il s'agit d'un itérateur. Dans ce cas, bien sûr vous pouvez :
>>> ge.next() 0 >>> ge.next() 1 >>> ge.next() 2 >>> ge.next() Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
Comme vous pouvez le voir sur cet exemple simple, la valeur de retour de la fonction contenant le mot-clé rendement est un objet de type générateur, et cet objet générateur est un itérateur.
Nous appelons des fonctions contenant des générateurs d'instructions de rendement. Un générateur est un itérateur défini avec une syntaxe de fonction ordinaire. Comme le montre l'exemple ci-dessus, pendant le processus de définition de ce générateur (également un itérateur), __inter__() et next() ne sont pas écrits comme l'itérateur de la section précédente, tant que l'instruction rendement est utilisée. , la fonction ordinaire. Elle devient comme par magie un générateur et possède les caractéristiques fonctionnelles d'un itérateur. La fonction de l'instruction
yield est de renvoyer la valeur correspondante lorsqu'elle est appelée. Analysons en détail le processus en cours ci-dessus :
1. ge = g() : à l'exception du retour du générateur, il n'y a aucune opération et aucune valeur n'est renvoyée.
2. ge.next() : Ce n'est qu'à ce moment-là que le générateur commence à s'exécuter lorsqu'il rencontre la première instruction rendement, il renvoie la valeur et suspend l'exécution (certains l'appellent
).为挂起)。
3. ge.next() :从上次暂停的位置开始,继续向下执行,遇到 yield 语句,将值返回,又暂停。
4. gen.next() :重复上面的操作。
5. gene.next() :从上面的挂起位置开始,但是后面没有可执行的了,于是 next() 发出异常。
从上面的执行过程中,发现 yield 除了作为生成器的标志之外,还有一个功能就是返回值。那么它跟 return 这个返回值有什么区别呢?
为了弄清楚 yield 和 return 的区别,我写了两个函数来掩饰:
>>> def r_return(n): ... print "You taked me." ... while n > 0: ... print "before return" ... return n ... n -= 1 ... print "after return" ... >>> rr = r_return(3) You taked me. before return >>> rr 3
从函数被调用的过程可以清晰看出, rr = r_return(3) ,函数体内的语句就开始执行了,遇到 return,将值返
回,然后就结束函数体内的执行。所以 return 后面的语句根本没有执行。这是 return 的特点
下面将 return 改为 yield:
>>> def y_yield(n): ... print "You taked me." ... while n > 0: ... print "before yield" ... yield n ... n -= 1 ... print "after yield" ... >>> yy = y_yield(3) #没有执行函数体内语句 >>> yy.next() #开始执行 You taked me. before yield 3 #遇到 yield,返回值,并暂停 >>> yy.next() #从上次暂停位置开始继续执行 after yield before yield 2 #又遇到 yield,返回值,并暂停 >>> yy.next() #重复上述过程 after yield before yield 1 >>> yy.next() after yield #没有满足条件的值,抛出异常 Traceback (most recent call last): File "<stdin>", line 1, in <module> StopIteration
结合注释和前面对执行过程的分析,读者一定能理解 yield 的特点了,也深知与 return 的区别了。
一般的函数,都是止于 return。作为生成器的函数,由于有了 yield,则会遇到它挂起,如果还有 return,遇到它就直接抛出 SoptIteration 异常而中止迭代。
#!/usr/bin/env Python # coding=utf-8 def fibs(max): """ 斐波那契数列的生成器 """ n, a, b = 0, 0, 1 while n < max: yield b a, b = b, a + b n = n + 1 if __name__ == "__main__": f = fibs(10) for i in f: print i ,
运行结果如下:
$ python 21501.py 1 1 2 3 5 8 13 21 34 55
用生成器方式实现的斐波那契数列是不是跟以前的有所不同了呢?大家可以将本教程中已经演示过的斐波那契数列实现方式做一下对比,体会各种方法的差异。
经过上面的各种例子,已经明确,一个函数中,只要包含了 yield 语句,它就是生成器,也是迭代器。这种方式显然比前面写迭代器的类要简便多了。但,并不意味着上节的就被抛弃。是生成器还是迭代器,都是根据具体的使用情景而定。
在 python2.5 以后,生成器有了一个新特征,就是在开始运行后能够为生成器提供新的值。这就好似生成器
和“外界”之间进行数据交流。
>>> def repeater(n): ... while True: ... n = (yield n) ... >>> r = repeater(4) >>> r.next() 4 >>> r.send("hello") 'hello
当执行到 r.next() 的时候,生成器开始执行,在内部遇到了 yield n 挂起。注意在生成器函数中, n = (yield
n) 中的 yield n 是一个表达式,并将结果赋值给 n,虽然不严格要求它必须用圆括号包裹,但是一般情况都这
么做,请大家也追随这个习惯。
当执行 r.send("hello") 的时候,原来已经被挂起的生成器(函数)又被唤醒,开始执行 n = (yield n) ,也就是
讲 send() 方法发送的值返回。这就是在运行后能够为生成器提供值的含义。
如果接下来再执行 r.next() 会怎样?
>>> r.next()
什么也没有,其实就是返回了 None。按照前面的叙述,读者可以看到,这次执行 r.next() ,由于没有传入任何值,yield 返回的就只能是 None.
还要注意,send() 方法必须在生成器运行后并挂起才能使用,也就是 yield 至少被执行一次。如果不是这样:
>>> s = repeater(5) >>> s.send("how") Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: can't send non-None value to a just-started generator
就报错了。但是,可将参数设为 None:
>>> s.send(None) 5
这是返回的是调用函数的时传入的值。
此外,还有两个方法:close() 和 throw()
• throw(type, value=None, traceback=None):用于在生成器内部(生成器的当前挂起处,或未启动时在定
义处)抛出一个异常(在 yield 表达式中)。
• close():调用时不用参数,用于关闭生成器。
推荐学习:python视频教程
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!