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