Python控制台动画

在《流畅的python》中看到的程序在控制台上显示一个小动画:旋转指针。通过按顺序写入字符"|/-",让其看起来就像是字符在旋转一样。在此记录一下。作者用写了两个版本,线程版和协程版。这里贴出线程版。点击github访问

# BEGIN SPINNER_THREAD
import threading
import itertools
import time
import sys

class Signal: # <1>
    go = True

def spin(msg, signal):  # <2>
    write, flush = sys.stdout.write, sys.stdout.flush
    for char in itertools.cycle('|/-\\'):  # <3>
        status = char + ' ' + msg
        write(status)
        flush()
        time.sleep(.1)
        write('\x08' * len(status))  # <4>
        if not signal.go:
              break
    write(' ' * len(status) + '\x08' * len(status))  # <6>


def slow_function():  # <7>
    # pretend waiting a long time for I/O
    time.sleep(3)  # <8>
    return 42


def supervisor():  # <9>
    signal = Signal()
    spinner = threading.Thread(target=spin,
                               args=('thinking!', signal))
    print('spinner object:', spinner)  # <10>
    spinner.start()  # <11>
    result = slow_function()  # <12>
    signal.go = False  # <13>
    spinner.join()  # <14>
    return result


def main():
    result = supervisor()  # <15>
    print('Answer:', result)


if __name__ == '__main__':
    main()
# END SPINNER_THREAD

原文注解:

  1. 这个类定义一个简单的可变对象;其中有个go属性,用于从外部控制线程。
  2. 这个函数会在单独的线程中运行。signal参数是前面定义的Signal类的实例。
  3. 这其实是个无限循环,因为itertools . cycle函数会从指定的序列中反复不断地生成元素。
  4. 这是显示文本式动画的诀窍所在:使用退格符(\x08) 把光标移回来。
  5. 如果go属性的值不是True了,那就退出循环。
  6. 使用空格清除状态消息,把光标移回开头。
  7. 假设这是耗时的计算。
  8. 调用sleep函数会阻塞主线程,不过一-定要这么做,以便释放GIL, 创建从属线程。
  9. 这个函数设置从属线程,显示线程对象,运行耗时的计算,最后杀死线程。
  10. 显示从属线程对象。输出类似于
  11. 启动从属线程。
  12. 运行slow_ _function函数, 阻塞主线程。同时,从属线程以动画形式显示旋转指针。
  13. 改变signal的状态;这会终止spin函数中的那个for循环。
  14. 等待spinner线程结束。
  15. 运行supervisor函数。

虽然作者已经给了比较详细的解释,不过,还是有部分代码作者没说。可能会疑惑
sys.out.write 和 sys.stdout.flush 是什么。
在此之前,先看下我们熟悉的print,虽然我们经常用print输出打印直接显示。不过你知道可以用print 来写入文件吗?

#print(*objects, sep=' ', end='\n', file=sys.stdout, flush=False)
print('this is a file',file=open('test.txt', 'w'))

运行上面代码,你会发现,在屏幕上看不到字符 ‘this is a file’,不过你在文件目录会发现新建的一个文本test.txt。里面内容是‘this is a file’。这是因为默认情况下,print,是将打印内容写入标准输出(sys.stdout)也就是控制台。而上面代代码重新设置print默认参数file=open('test.txt', 'w'),即将字符输入到了'test.txt'。现在应该猜到了吧,print 是sys.stdout 的一个封装,高级接口。标准输出指控制台了。
那flush是什么呢。分别将flush(),去掉注释和加上运行看一下

def spin(msg, signal):  # <2>
    write, flush = sys.stdout.write, sys.stdout.flush
    for char in itertools.cycle('|/-\\'):  # <3>
        status = char + ' ' + msg
        write(status)
        #flush()
        #write('\x08' * len(status))  # <4>
        time.sleep(.1)
        if not signal.go:
              break
    #write(' ' * len(status) + '\x08' * len(status))  # <6>

会发现,注释掉时控台上将每个指针状态都一个接一个的打印出来了。而不注释的话,会等时间到到一次性全部打印出啦。这是因为sys.stdout.write是将字符先写入缓冲区,然后等缓冲区满了,或进行强制刷新缓冲区或者程序结束才会从缓冲区输出。而flush就是用于强制刷新缓冲区的。缓冲区是干嘛的?缓冲区用于避免重复执行昂贵的操作,提高程序效率的。详情参考What is output buffering in Python?.
那默认print参数中flush=False,将其改为True是不是也可以实现动画呢?

def spin(msg, signal):  # <2>
    for char in itertools.cycle('|/-\\'):  # <3>
        status = char + ' ' + msg
        print(status,flush=True)
        print('\x08' * len(status))  # <4>
        time.sleep(.1)
        if not signal.go:
              break
    #write(' ' * len(status) + '\x08' * len(status))  # <6>

实际上,会将每个状态一行一行的打印出来,这是因为print默认参数中end='\n',在sys.stdout的缓冲中遇到换行就会立即输出打印。设置成end=''就可以了。

def spin(msg, signal):  # <2>
    for char in itertools.cycle('|/-\\'):  # <3>
        status = char + ' ' + msg
        print(status,end='',flush=True)
        print('\x08' * len(status),end='')  # <4>
        time.sleep(.1)
        if not signal.go:
              break
    #write(' ' * len(status) + '\x08' * len(status))  # <6>

你可能感兴趣的:(Python控制台动画)