首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >python生成器函数的应用场景举例---为copy过程添加进度条显示

python生成器函数的应用场景举例---为copy过程添加进度条显示

作者头像
qsjs
发布2020-09-28 15:51:21
1.1K0
发布2020-09-28 15:51:21
举报

首先看一个最简单的生成器,并判断是否是生成器,代码如下:

>>> from inspect import isgenerator
>>> g=(n for n in range(10))
>>> isgenerator(g)
True
>>> 

生成器的特点是可以迭代,通过dir 查看生成器的方法, 其中有next , send 方法,我们如果调用其next或者send方法都可以获得其下一个元素的值,我们可以用这种方式获得所有的生成器对应的元素,直到抛出 StopIteration 异常为止。一旦某个值已经输出了,那么我们是无法进行回溯的,就像一个“人生的单行道”一样,只能向前走,无法向后退。

>>> dir(g)
['__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']

这种next或者send的方法,可以看做是让 代码的执行暂停了,怎么个暂停法呢? 在上面的生成器例子中,当调用next或者send方法的时候,会输出一次变量n的值,然后就暂停执行了,我们暂且这么理解,当再次调用next 或者send 方法的时候,就从暂停的地方继续执行直到再次输出变量n的值并暂停,每次调用next或者send都是如此,一直到发生StopIteration 为止. 而如果直接访问g, 那么不会输出任何的值, 仅仅提示 g 是一个生成器而已.

首先来尝试做一个简单的生成器函数:

首先我们要了解函数中的一个关键字yield,其有以下的能耐: a. yield有 return 关键字的作用,所以可以用 "yield VAULE" 这种表达式来返回VAULE作为函数的返回值. b. 比return 更强的是,遇到yield 表达式后,会执行yield 表达式(比如上面描述的yield VAULE, 当然也可以是只有 yield 这个关键字的表达式),然后函数的执行就暂停了. c. 表达式都是可以把执行结果赋值给变量的,所以yield 表达式也是,但是yield 通常实现的是类似return 的效果,所以这个表达式本身的结果看起来总是"None",也就是说,你如果用 v=yield VAULE 或者 v=yield 这种方式,那么v始终是“None”. 但是如果调用函数的send方法,那么send方法的参数就变成了yield 表达式的值;利用此特性,可以成功的从外部传递变量给函数内部变量. d. 利用上述 a,b,c 的描述, 我们可以利用yield实现 :函数的执行过程暂停,并且在暂停后返回需要的值,在恢复执行的时候,传递新的值作为函数的参数, 这也是 生成器函数的特性. 下面的代码展示了一个利用上述三种特性的例子(没有进行StopIteration 异常的处理):

#!/usr/bin/env python
def genefunc():
    print("You can meet me only at the begninning of the function!")
        sum=0
    for n in range(10):
        print("Got From Generator Function>: ",n)
        t=yield n
        sum+=t
                sum+=n
        print("Sum of all Passed value and Generator value :",sum)
s=genefunc()  #函数并没有真正的执行
s.next()         #因为在生成器还没有开始运行的时候,是不能传递值进去的,所以需要先用next 来启动生成器.
for j in range(10,20):
    print("Pass to Generate Value: >:",j)
    s.send(j)
生成器函数到底有什么应用场景呢?

从上面的描述以及例子中可以知道,生成器函数的最大特点是“函数的执行可以中断和恢复,并且在中断的时候返回值,在恢复的时候可以接受新的参数值”,所以 遇到如下逻辑就都可以用生成器函数来实现:两个或者多个 可以重复的过程 需要交互 ,并且这种交互存在明确的先后依赖。 网上例子比较多的是:吃包子的例子. 这里不再赘述.

在这里展示一个copy过程的例子,现在我们需要以进度条的方式显示copy的进度,正常情况下,一个线程实现copy, 另一个线程计算已经copy的文件和待copy的文件, 然后进行比较,从而输出进度条,也就是说至少需要两个线程. 而用 生成器函数一个线程就可以了,因为每次copy完成一个文件后,就暂停copy操作,转而去进行一个copy进度的计算, 进度计算完并显示滚动条,然后回到copy的操作, 其实是一个copy动作和 进度条计算的交互过程在一个进程中的实现. 下面是上述思路的一种参考代码(进度条的显示和计算都是在 生成器函数中实现,外面函数仅仅传递已经完成copy的文件数量,并在适当的时候停止对生成器函数的调用.):

#!/usr/bin/env python
# -*- coding:utf-8 -*- 
#Author: PandaEye

import sys,os,time
def copy_action(spath,dpath):
    s_count=eval(os.popen("ls "+spath+" | wc -l").next().replace("\n",""))  #Nubmers of files to be copied.
    for i in os.popen("ls "+spath): 
        src="cp -afr "+spath+"/"+i  
        os.system(src.replace("\n","")+" "+dpath + " 2>/dev/null")  
        d_count=yield  #Once one file/folder copy completed, then stop the execution.
        d=d_count*100/s_count
        sys.stdout.write("\r"+"#"*d+" %"+"%d "  % d) #"\r" make the output always clean the current line.so it looks nice.
        sys.stdout.flush()

src,dst="/sbin","/dev/shm"
g=copy_action(src,dst) 
g.next() 
s_time=time.time() #Record for start time. 
d_count=0
while True:
    try:
        g.send(d_count) 
        d_count+=1
    except StopIteration:
        break
sys.stdout.write("\r"+"#"*100+" %100 ")
sys.stdout.flush()
e_time=time.time()  #Record for the stop time.
print("\nElapsed time: %fs" % (e_time-s_time))

本文原创,转载请注明出处!

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 首先来尝试做一个简单的生成器函数:
  • 生成器函数到底有什么应用场景呢?
  • 本文原创,转载请注明出处!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档