在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使用生成器方式。