Maison > développement back-end > Tutoriel Python > Introduction détaillée au rendement et au générateur en python

Introduction détaillée au rendement et au générateur en python

Y2J
Libérer: 2017-04-27 11:55:38
original
1195 Les gens l'ont consulté

Cet article explique principalement les informations relatives au rendement et au générateur en python du moins profond au plus profond. L'introduction dans l'article est très détaillée et a une certaine valeur de référence pour tous les amis qui en ont besoin peuvent y jeter un œil ci-dessous.

Avant-propos

Cet article présentera le rendement et le générateur en détail, du plus simple au plus approfondi, y compris le contenu suivant : quel générateur, comment générer générateur et fonctionnalités du générateur, scénarios d'application de base et avancés du générateur et précautions lors de l'utilisation du générateur. Cet article n'inclut pas le générateur amélioré ni le contenu lié à pep342, cette partie sera introduite plus tard.

Bases du générateur

Dans la définition de la fonction de python, tant que l'expression de rendement apparaît, alors en fait ce qui est défini est un générateur fonction, et la valeur de retour de l'appel de ceci generator function est un générateur. Cet appel de fonction ordinaire est différent. Par exemple :

def gen_generator():
 yield 1
def gen_value():
 return 1
 
if __name__ == '__main__':
 ret = gen_generator()
 print ret, type(ret) #<generator object gen_generator at 0x02645648> <type &#39;generator&#39;>
 ret = gen_value()
 print ret, type(ret) # 1 <type &#39;int&#39;>
Copier après la connexion

Comme le montre le code ci-dessus, la fonction gen_generator renvoie une instance de générateur

générateur. fonctionnalités spéciales suivantes :

•Suivez le protocole de l'itérateur, qui doit implémenter __iter__ , interface suivante

•Peut passer plusieurs fois Saisir et renvoyer plusieurs les fois peuvent suspendre l'exécution du code dans le corps de la fonction

Jetons un coup d'œil au code de test :

>>> def gen_example():
... print &#39;before any yield&#39;
... yield &#39;first yield&#39;
... print &#39;between yields&#39;
... yield &#39;second yield&#39;
... print &#39;no yield anymore&#39;
... 
>>> gen = gen_example()
>>> gen.next()    # 第一次调用next
before any yield
&#39;first yield&#39;
>>> gen.next()    # 第二次调用next
between yields
&#39;second yield&#39;
>>> gen.next()    # 第三次调用next
no yield anymore
Traceback (most recent call last):
 File "<stdin>", line 1, in <module>
StopIteratio
Copier après la connexion

L'appel de la méthode exemple gen ne produit pas de résultat n'importe quoi , indiquant que le code du corps de la fonction n'a pas encore commencé son exécution. Lorsque la méthode suivante du générateur est appelée, le générateur s'exécutera sur l'expression de rendement, renverra le contenu de l'expression de rendement, puis fera une pause (se bloquera) à cet endroit, de sorte que le premier appel à next imprime la première phrase et renvoie " premier rendement". La pause signifie que les variables locales, les informations du pointeur et l'environnement d'exécution de la méthode sont enregistrés jusqu'à ce que la méthode suivante soit appelée pour reprendre. Après avoir appelé next pour la deuxième fois, elle s'arrête au dernier rendement. Si la méthode next() est à nouveau appelée, une exception StopIteration sera levée.

Étant donné que l'instruction for peut capturer automatiquement l'exception StopIteration, la méthode de générateur la plus courante (essentiellement n'importe quel itérateur) consiste à l'utiliser dans une boucle :

def generator_example():
 yield 1
 yield 2

if __name__ == &#39;__main__&#39;:
 for e in generator_example():
 print e
 # output 1 2
Copier après la connexion

généré par fonction générateur Quelle est la différence entre un générateur et une fonction normale ?

(1) La fonction commence à s'exécuter à partir de la première ligne à chaque fois, tandis que le générateur s'exécute à partir du début du dernier rendement

(2) Un appel de fonction renvoie une (un ensemble de) valeurs à la fois, et un générateur peut renvoyer

plusieurs fois (3) Une fonction peut être appelée à plusieurs reprises un nombre incalculable de fois, mais une instance de générateur ne peut pas produire la dernière valeur ou revenir après celle-ci. Continuez à appeler

pour utiliser Yield dans la fonction, puis appeler la fonction est un moyen de générer un générateur. Une autre méthode courante consiste à utiliser generator expression, par exemple :

  >>> gen = (x * x for x in xrange(5))
  >>> print gen
  <generator object <genexpr> at 0x02655710>
Copier après la connexion

application génératrice

application de base génératrice

Pourquoi utiliser le générateur ? La raison la plus importante est qu'il peut générer et "retourner" des résultats à la demande au lieu de générer toutes les valeurs de retour à la fois. De plus, parfois vous ne connaissez pas "tous les retours". valeur du tout".

Par exemple, pour le code suivant 

RANGE_NUM = 100
 for i in [x*x for x in range(RANGE_NUM)]: # 第一种方法:对列表进行迭代
 # do sth for example
 print i

 for i in (x*x for x in range(RANGE_NUM)): # 第二种方法:对generator进行迭代
 # do sth for example
 print i
Copier après la connexion

Dans le code ci-dessus, la sortie des deux instructions for est la même, et le code signifie littéralement carré brackets La différence avec les parenthèses. Mais cette différence est très différente. La première méthode renvoie une liste et la seconde méthode renvoie un objet générateur. À mesure que RANGE_NUM augmente, la liste renvoyée par la première méthode devient plus grande et la mémoire occupée devient plus grande mais il n'y a aucune différence pour la seconde méthode ;

Regardons un autre exemple qui peut « renvoyer » un nombre infini de fois :

def fib():
 a, b = 1, 1
 while True:
 yield a
 a, b = b, a+b
Copier après la connexion

Ce générateur a la capacité de générer d'innombrables « valeurs de retour », et l'utilisateur peut décider quand pour arrêter l'itération

Application avancée du générateur

Scénario d'utilisation un :

Le générateur peut être utilisé pour générer des flux de données, le générateur ne génère pas de valeur de retour immédiatement, mais attend qu'elle soit nécessaire. Cela équivaut à un processus d'extraction actif (pull). Par exemple, il existe un fichier journal et chaque ligne génère un. Pour chaque enregistrement, les personnes des différents services peuvent les gérer différemment, mais nous pouvons fournir un flux de données commun à la demande.

def gen_data_from_file(file_name):
 for line in file(file_name):
 yield line

def gen_words(line):
 for word in (w for w in line.split() if w.strip()):
 yield word

def count_words(file_name):
 word_map = {}
 for line in gen_data_from_file(file_name):
 for word in gen_words(line):
  if word not in word_map:
  word_map[word] = 0
  word_map[word] += 1
 return word_map

def count_total_chars(file_name):
 total = 0
 for line in gen_data_from_file(file_name):
 total += len(line)
 return total
 
if __name__ == &#39;__main__&#39;:
 print count_words(&#39;test.txt&#39;), count_total_chars(&#39;test.txt&#39;)
Copier après la connexion

L'exemple ci-dessus provient d'une conférence à PyCon en 2008. gen_words gen_data_from_file est le producteur de données et count_words count_total_chars est le consommateur de données. Comme vous pouvez le constater, les données ne sont extraites qu’en cas de besoin, plutôt que préparées à l’avance. De plus, (w for w in line.split() if w.strip()) dans gen_words génère également un générateur

Scénario d'utilisation deux :

.

一些编程场景中,一件事情可能需要执行一部分逻辑,然后等待一段时间、或者等待某个异步的结果、或者等待某个状态,然后继续执行另一部分逻辑。比如微服务架构中,服务A执行了一段逻辑之后,去服务B请求一些数据,然后在服务A上继续执行。或者在游戏编程中,一个技能分成分多段,先执行一部分动作(效果),然后等待一段时间,然后再继续。对于这种需要等待、而又不希望阻塞的情况,我们一般使用回调(callback)的方式。下面举一个简单的例子:

 def do(a):
 print &#39;do&#39;, a
 CallBackMgr.callback(5, lambda a = a: post_do(a))
 
 def post_do(a):
 print &#39;post_do&#39;, a
Copier après la connexion

这里的CallBackMgr注册了一个5s后的时间,5s之后再调用lambda函数,可见一段逻辑被分裂到两个函数,而且还需要上下文的传递(如这里的参数a)。我们用yield来修改一下这个例子,yield返回值代表等待的时间。

 @yield_dec
 def do(a):
 print &#39;do&#39;, a
 yield 5
 print &#39;post_do&#39;, a
Copier après la connexion

这里需要实现一个YieldManager, 通过yield_dec这个decrator将do这个generator注册到YieldManager,并在5s后调用next方法。Yield版本实现了和回调一样的功能,但是看起来要清晰许多。

下面给出一个简单的实现以供参考:

# -*- coding:utf-8 -*-
import sys
# import Timer
import types
import time

class YieldManager(object):
 def __init__(self, tick_delta = 0.01):
 self.generator_dict = {}
 # self._tick_timer = Timer.addRepeatTimer(tick_delta, lambda: self.tick())

 def tick(self):
 cur = time.time()
 for gene, t in self.generator_dict.items():
  if cur >= t:
  self._do_resume_genetator(gene,cur)

 def _do_resume_genetator(self,gene, cur ):
 try:
  self.on_generator_excute(gene, cur)
 except StopIteration,e:
  self.remove_generator(gene)
 except Exception, e:
  print &#39;unexcepet error&#39;, type(e)
  self.remove_generator(gene)

 def add_generator(self, gen, deadline):
 self.generator_dict[gen] = deadline

 def remove_generator(self, gene):
 del self.generator_dict[gene]

 def on_generator_excute(self, gen, cur_time = None):
 t = gen.next()
 cur_time = cur_time or time.time()
 self.add_generator(gen, t + cur_time)

g_yield_mgr = YieldManager()

def yield_dec(func):
 def _inner_func(*args, **kwargs):
 gen = func(*args, **kwargs)
 if type(gen) is types.GeneratorType:
  g_yield_mgr.on_generator_excute(gen)

 return gen
 return _inner_func

@yield_dec
def do(a):
 print &#39;do&#39;, a
 yield 2.5
 print &#39;post_do&#39;, a
 yield 3
 print &#39;post_do again&#39;, a

if __name__ == &#39;__main__&#39;:
 do(1)
 for i in range(1, 10):
 print &#39;simulate a timer, %s seconds passed&#39; % i
 time.sleep(1)
 g_yield_mgr.tick()
Copier après la connexion

注意事项:

(1)Yield是不能嵌套的!

def visit(data):
 for elem in data:
 if isinstance(elem, tuple) or isinstance(elem, list):
  visit(elem) # here value retuened is generator
 else:
  yield elem
  
if __name__ == &#39;__main__&#39;:
 for e in visit([1, 2, (3, 4), 5]):
 print e
Copier après la connexion

上面的代码访问嵌套序列里面的每一个元素,我们期望的输出是1 2 3 4 5,而实际输出是1 2 5 。为什么呢,如注释所示,visit是一个generator function,所以第4行返回的是generator object,而代码也没这个generator实例迭代。那么改改代码,对这个临时的generator 进行迭代就行了。

def visit(data):
 for elem in data:
 if isinstance(elem, tuple) or isinstance(elem, list):
  for e in visit(elem):
  yield e
 else:
  yield elem
Copier après la connexion

或者在python3.3中 可以使用yield from,这个语法是在pep380加入的

 def visit(data):
 for elem in data:
  if isinstance(elem, tuple) or isinstance(elem, list):
  yield from visit(elem)
  else:
  yield elem
Copier après la connexion

(2)generator function中使用return

在python doc中,明确提到是可以使用return的,当generator执行到这里的时候抛出StopIteration异常。

def gen_with_return(range_num):
 if range_num < 0:
 return
 else:
 for i in xrange(range_num):
  yield i

if __name__ == &#39;__main__&#39;:
 print list(gen_with_return(-1))
 print list(gen_with_return(1))
Copier après la connexion

但是,generator function中的return是不能带任何返回值的


 def gen_with_return(range_num):
 if range_num < 0:
  return 0
 else:
  for i in xrange(range_num):
  yield i
Copier après la connexion

上面的代码会报错:SyntaxError: 'return' with argument inside generator

总结

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!

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
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
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal