终端进度条的简单原理

written on Sun 27 March 2016 by

进度条

每次使用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感觉很麻烦? 几种方式轻松解决!

  1. python -u ProcessBar.py 带参数u来运行文件
  2. PYTHONUNBUFFERED=1 python ProcessBar.py 与方法一等效
  3. #!/usr/bin/env python -u linux、unix需要直接运行的方式的话,可以在py文件开头加上-u
  4. 使用stderr.write(str)输出,因为stderr不使用缓冲区

This entry was tagged on #Python

comments powered by Disqus
 

Tags