非常棒的进度条
在ftp项目编写上传下载文件时候会用到进度条功能,以便客户机直观的获取文件传输状态信息。python已经由第三方tqdm库供我们调用了,但是这么一个简单的功能调用一个200多k的第三方模块让我觉得相当浪费,不如自己写一个吧!
需求:
require1>>>文件传输中,每当文件大小状态获得更新时,我需要在屏幕的进度条上得到实时反馈
require2>>>我希望我的进度条能在一行展示,而不是铺满整个屏幕
solve1:
第一个需求让我想到生产者消费者模型,只是这里只有一个生产者和一个消费者
solve2:
print方法默认结束符end='\n',即下次打印会换行:
import time for i in range(5): time.sleep(0.2) print(i)
# 如下图,这种打印方式显然不符合要求
而sys.stdout.write没有end一说,下次打印不会换行,更符合需求
import sys import time for i in range(10): time.sleep(0.2) sys.stdout.write(str(i))
# 如下图,这种方式能实现单行打印,但是为了提高效率,sys.stdout.write方法会等所有数据缓存满了再一次性输出,
解决办法:sys.stdout.flush(),每来一次数据就强制输出一次,而不是默认所有数据一起输出
import sys import time for i in range(10): time.sleep(0.2) sys.stdout.write(str(i)) sys.stdout.flush()
# 还是有问题:每次打印我只要从开始位置显示当前数据,之前数据不显示
解决办法:在每次打印数据前加 '\r' 表示从当前行起始位置打印
import sys import time for i in range(10): time.sleep(0.2) sys.stdout.write('\r' + str(i)) sys.stdout.flush() # 如下图,这样就比较完美了
最终实现代码
code1:
# -*- coding:utf-8 -*- # Author: Tarantiner # @Time :2019/3/26 11:53 import time import sys import queue from threading import Thread total_size = 36842 # 模拟文件总大小 rec_size = 0 # 模拟接收到文件大小 MAX = 50 # 进度条中显示最大方块数量 def fetcher(): # 实现进度条打印功能 while True: k = q.get() if k is None: print(' Done!') break sys.stdout.write('\r' + '|' + '▇' * k + '|' + str(int(k*100/MAX)) + '%') def producer(q): global rec_size count = 0 while rec_size < total_size: time.sleep(0.1) rec_size += 1024 # 模拟获取数据 count = int(rec_size * MAX / total_size) q.put(count) q.put(count) q.put(None) # 发送结束信号 if __name__ == '__main__': q = queue.Queue() f = Thread(target=fetcher) f.start() producer(q)
输出:
想想code1中为单个生产者消费者开辟一个子线程,有点不值,于是又想到了生成器,实现单个线程下程序级别的切换,开销更小,于是就有了code2
code2
# -*- coding:utf-8 -*- # Author: Tarantiner # @Time :2019/3/26 12:25 import time total_size = 100 rec_size = 0 count = 0 def func(): while True: k = yield # sys.stdout.write只是print的一种特定格式,那么print当然能实现sys.stdout.write功能 print('\r' + '|' + '▇'*k + '|' + str(k) + '%', end='', flush=True) f = func() next(f) while rec_size < total_size: f.send(count) time.sleep(0.2) rec_size += 2 count = int(rec_size * 50 / total_size) f.send(count)
至此,进度条问题就完美解决了,当然我会推荐code2使用生成器方式。