休息一下

长期在电脑前边工作,对视力的影响很大。我们需要一些手段提醒自己在工作一段时间后要注意休息。市面上当然有很多相关的定时休息软件,我也推荐使用它们。事实上,如果只是需要一个倒计时的小工具,我们完全可以自己简单写一个。asyncio 官方文档的 18.5.1.18.2 中就提供了一个很简单的倒计时的实例代码,我们在这里稍加改编来完成我们的需求。

我们的需求如下:

  • 输入命令 take-a-rest ,进入倒计时模式
  • 每秒钟界面上会刷新还剩多少秒
  • 可以通过环境变量配置一次休息多少秒

实现它的代码很简单,如下所示,将这段代码存为 take-a-rest

#!/usr/bin/env python3

import os
import sys
import asyncio

REST_SECONDS = int(os.environ.get('REST_SECONDS') or 60)

def report(left_time):
    sys.stdout.write("Resume working after %d seconds. \r" % (left_time))
    sys.stdout.flush()

def rest(end_time, loop):
    current_time = loop.time()
    report(end_time - current_time)
    if (current_time + 1.0) < end_time:
        loop.call_later(1.0, rest, end_time, loop)
    else:
        loop.stop()

loop = asyncio.get_event_loop()
loop.call_soon(rest, loop.time() + REST_SECONDS, loop)
loop.run_forever()
loop.close()

为了使用方便,我们通过执行命令 chmod +x take-a-rest 为这个脚本赋予可执行的属性,并将这个文件移动到 $PATH 所在的目录中。现在,我们可以直接在命令行中输入命令,就能进入倒计时模式:

$ take-a-rest
Resume working after 60 seconds.

我们通过 int(os.environ.get('REST_SECONDS') or 60) 获得环境变量中的配置,并设置默认值为 60.

函数 report(left_time) 的功能是在命令行中显示目前还剩多少秒可以回到工作。特别地,字符 \r 可以让输出结束后的光标回到行首。通过这种方法,我们可以在新的一次调用中将上一次的输出替换掉。

函数 rest(end_time, loop) 的功能是决定是否还继续倒数。当尚未到达倒数时间点,我们继续触发一次 rest(end_time, loop);当到达倒数时间点,我们停止运行事件循环。换句话说,当 loop.stop() 调用后,loop.run_forever() 也将停止。最后我们关闭事件循环,程序结束。

函数调用loop.time()的返回值是类型为浮点数字的当前时间,它取决于事件循环的内部时钟。

上述的代码其实隐含地包括了一段逻辑:执行一个函数,当调用结束时,停止事件循环。我们可以使用 loop.run_until_complete 函数简化它:

#!/usr/bin/env python3

import os
import sys
import asyncio

REST_SECONDS = int(os.environ.get('REST_SECONDS') or 60)

def report(left_time):
    sys.stdout.write("Resume working after %d seconds. \r" % (left_time))
    sys.stdout.flush()

async def rest():
    for left_time in range(REST_SECONDS, 0, -1):
        report(left_time)
        await asyncio.sleep(1)

loop = asyncio.get_event_loop()
loop.run_until_complete(rest())
loop.close()

函数调用 loop.run_until_complete(future) 不像上面使用的 loop.run_forever() 一直执行下去,而是一直执行直到传入的 future 对象执行完毕。如果传入的是个协程对象,asyncio 会使用 ensure_future() 将这个协程对象转为 future 对象。

我很喜欢这个小工具。为了更好地提示自己该休息了,我使用了一段 bash 主题的配置,它会按照我的休息时间提示我是否该休息了。有关这段主题配置的代码我放在了 gist 上,前往阅读