Linux运维进阶-Python,Docker,Shell

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

2020-09-26  本文已影响0人  My熊猫眼

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

>>> 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" 这种表达式实现return 的效果,那么 什么情况下才能获取到返回的值呢? 调用 next方法的时候,其实就是执行到 yield 语句,然后返回对应值;
b.
从上一条可以看到,遇到yield 表达式后,会执行yield 表达式,然后函数的执行就暂停了,也就是yield 这一行后面的语句将暂时停止执行.
c.
yield表达式可以赋值给变量,但是变量接收到的值却不是yield 表达式的值,而是 生成器函数的send方法的参数值;
所以通过yield 表达式,既可以执行 return 相同的作用 (next方法调用来实现),也可以接受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))

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

上一篇下一篇

猜你喜欢

热点阅读