终端进度条的简单原理
written on Sun 27 March 2016 by importcjj
进度条
每次使用pip安装一些第三方库时,总会在终端或者命令行中看到进度条,能够让用户等待时得到当前任务的进度。今天偶然在知乎看到一个帖子中提到了这个有趣的功能。
实现方式
一般情况下命令行和终端的输出总是由上往下一行一行输出。如果我们需要实现进度条的效果的效果的话,就需要每次输出都在同一行上,不断地用后来的输出去覆盖之前的输出,从而模拟出一种动态变化的效果。那究竟怎样实现呢?答案就是使用反杠r。
反杠n是换行,反杠r就只是把输出光标移到行首,而不换行。根据这个思路,我们先做个倒计时来玩玩。
import time
import sys
def CountDown(total_sec):
for i in xrange(10, -1, -1):
# 不能换行哈
print '%4d\\r' % i,
sys.stdout.flush()
time.sleep(1)
# 防止输出被之后的输出覆盖
print
CountDown(10)
结果让我失望了,因为倒计时效果没有出现,而是等待10秒后直接出现0
为什么?
输出是有缓存机制的,输出缓存(即所谓的行缓冲区)是根据换行符来写数据的,看到换行符,就会从缓存打印数据。
修改代码前,先看下命令行和终端中的输出方式:
# print方式
print 'ProcessBar'
# stdout方式
import sys
sys.stdout.write('ProcessBar\\n')
print使用的较为普遍,但以上两种方式其实是等价的,无非是print自带默认自带换行。我们可以借助sys模块中stdout中的flush强制取出缓冲区的内容。
import time
import sys
def CountDown(total_sec):
for i in xrange(10, -1, -1):
# 默认不换行
# 也可以 print '%4d\\r' % i,
sys.stdout.write('%4d\\r' % i)
# 强制打印
sys.stdout.flush()
time.sleep(1)
# 防止最后结果被之后的输出覆盖
print
CountDown(10)
成功实现。接下来来实现进度条!
#!/usr/bin/env python
#-*-coding=utf-8-*-
import sys
class ProcessBar(object):
def __init__(self, end, char='#'):
"""
@param end : 总大小
@param char : 进度条的符号
"""
self.end = end
self.char = char
def show(self, count):
"""
@param count : 当前进度
"""
# 百分比
percent = count * 100 / self.end
# 符号数量
s_num = percent / 10
sys.stdout.write('%3d%%=>|%-10s|\\r' % (percent, self.char * s_num))
sys.stdout.flush()
if __name__ == '__main__':
import time
pb = ProcessBar(100)
for i in xrange(1, 101):
pb.show(i)
time.sleep(0.1)
print "\\ncompleted"
一个简单的进度条做好了,感觉还不错!
还有一种方法那就是使用反杠b来回退,有空试试。
扩展
每次输出都需要flush感觉很麻烦? 几种方式轻松解决!
- python -u ProcessBar.py 带参数u来运行文件
- PYTHONUNBUFFERED=1 python ProcessBar.py 与方法一等效
- #!/usr/bin/env python -u linux、unix需要直接运行的方式的话,可以在py文件开头加上-u
- 使用stderr.write(str)输出,因为stderr不使用缓冲区