0、问题描述
对于Python3的初学者来说,大部分时候,都是在写一些简单的控制台(console)程序。但如果你掌握了curses的用法,那么你就可以让你的程序表现丰富起来。利用curses,在console上,你就可以做一些简单的有交互性的应用,甚至做一些简单的游戏。以后,我们向大家展示如何使用curses做一些经典的游戏。
本文将教你如何用curses,在console实现一个时钟。这个时钟在控制台(屏幕)中展示当前的日期、星期和时间,秒数还会跳动。(效果请看封面图片)
不过,在阅读本文之前,你可能需要首先掌握datetime和curses的基本用法。以下是我推荐的几个介绍datetime和curses的网站,你可以各挑一个阅读了解。
介绍datetime的中文博客:
Python datetime模块详解www.cnblogs.comdatetime的Python官网文档:
datetime - Basic date and time types - Python 3.8.2 documentationdocs.python.org介绍curses的中文博客:
CSDN-专业IT技术社区-登录blog.csdn.net 【译】Python Curses 编程www.jianshu.comcurses的Python官网文档:
Curses Programming with Pythondocs.python.org如果你使用的是Linux操作系统,并且安装了Python3,那么你很有可能已经可以直接使用curses库了。我使用的是ubuntu,安装的Python3是自带安装了curses库的。
但如果你使用windows,你需要自己安装curses。在下载curses的windows包的时候,一定要选择和你的Python版本相同的curses包。以下是一篇关于curses安装的中文博客:
Windows安装Python3 curses模块www.cnblogs.com本文将用面向过程(POP)和面向对象(OOP)两种方式来实现控制台时钟的功能。为了更加直观地展示各个细节的实现步骤和原理,POP版本会用比较简单粗暴的方式去实现。在OOP中,我们会对POP版本的一些代码进行优化,并引用一些POP版本没有用到的curses特性。
1、curses的基本用法
这个小节会对curses的使用进行一个简单的介绍,如果你想更好的了解curses,请阅读我在上一个小节中推荐的文章。
在使用curses的时候,有一个通用的流程。启动的时候,包括创建curses屏幕对象,一些基本的设置;结束的时候,撤销原来的设置,最后关闭屏幕对象。通用流程如下面代码所示:
# 导入curses模块
import curses
def SayHello(stdscr):
# 使用addstr将字符串输出的屏幕
# 注意:第一个参数是y坐标,第二个参数是x坐标
stdscr.addstr(0, 0, "Hello World!")
stdscr.addstr(1, 0, "Press Any Key To Exit...")
# 阻塞式等待键盘输入
stdscr.getch()
# 初始化屏蔽,得到屏蔽对象stdscr
# 正如你所理解的 scr将screen的前三个字母
stdscr = curses.initscr()
# 设置一
# 监听屏蔽输入,并且不在屏蔽显示输入的字符
curses.noecho()
# 设置二
# 在用户不敲击回车键的情况下,直接影响用户的键盘输入
curses.cbreak()
# 设置三
# 监听特殊键,如左向键、向右键、PgUp、PgDn等
stdscr.keypad(True)
# 经过以上步骤,你就可以操作屏蔽,展示你的内容啦
SayHello(stdscr)
# 程序结束之前,你必须对之前的几步设置进入反向操作
# 否则,控制的显示会出错乱
curses.nocbreak() # 设置三反操作
stdscr.keypad(False) # 设置二反操作
curses.echo() # 设置一反操作
# 关闭屏蔽
curses.endwin()
因为这个流程很常见,所以curses库提供了wrapper方法。wrapper会帮我们处理好屏幕的创建和关闭、屏幕的设置和反设置等的相关步骤, 我们只需要实现跟自己功能相关的逻辑就可以了。所以上面的功能,可以简化成下面的实现:
# 导入curses模块
import curses
def SayHello(stdscr):
stdscr.addstr(0, 0, "Hello World!")
stdscr.addstr(1, 0, "Press Any Key To Exit...")
# 阻塞式等待键盘输入
stdscr.getch()
curses.wrapper(SayHello)
2、POP实现
如下代码是面向过程(POP)的实现。你可以将如下代码拷贝并存储一个文件中,可以取名字叫clock.py。打开命令行控制台,运行python3 clock.py命令就可以看到效果啦。如果你的系统只安装了Python3,那么可能你的命令应该是Python clock.py。
import curses
import datetime
import time
week_days = (
"+ Mon. +",
"+ Tue. +",
"+ Wed. +",
"+ Thu. +",
"+ Fri. +",
"+ Sta. +",
"+ Sun. +",
)
# 时钟的一次嘀嗒,每秒调用一次
def tick(stdscr):
# 获取当前时间
now = datetime.datetime.now()
# 获取时期,等同于 date_str = str(now.date())
date_str = "+ {}-{:02d}-{:02d} +".format(now.year, now.month, now.day)
# 获取星期的三字母缩写
week_str = week_days[now.weekday()]
# 获取时间,等同于 time_str = now.time().strftime('%H:%M:%S')
time_str = "+ {:02d}:{:02d}:{:02d} +".format(now.hour, now.minute, now.second)
# 计算起始坐标
y = (curses.LINES - 5) // 2
x = (curses.COLS - 14) // 2
# 使用addstr将字符串输出的屏幕
# 注意:第一个参数是y坐标
stdscr.addstr(y, x, "+" * 14)
stdscr.addstr(y + 1, x, date_str)
stdscr.addstr(y + 2, x, week_str)
stdscr.addstr(y + 3, x, time_str)
stdscr.addstr(y + 4, x, "+" * 14)
# 刷新屏幕
stdscr.refresh()
def main(stdscr):
while True:
tick(stdscr)
time.sleep(1)
if __name__ == "__main__":
curses.wrapper(main)
3、OOP实现
我们将POP的实现改造成OOP的方式。其实现和POP的基本一样。我们对POP的实现进行简化。
在POP实现中,程序运行之后,我们没有办法交互性地结束程序。如果你要结束程序,在Linux中你必须使用Ctrl+C;在Windows中,你必须使用Ctrl+D。在OOP中,我们加入了对用户输入(键盘)的监听,程序运行之后,我们可以通过敲击键盘的任意键结束程序。
import curses
import datetime
import time
week_days = (
"Mon.",
"Tue.",
"Wed.",
"Thu.",
"Fri.+",
"Sta.",
"Sun.",
)
class ConsoleClock:
# 重载此方法,让对象可以像函数一样被调用
# curses.wrapper(clock)会调用clock(stdscr)
# clock(stdscr)相当于 clock.__call__(stdscr)
def __call__(self, stdscr):
self.y = (curses.LINES - 5) // 2
self.x = (curses.COLS - 14) // 2
self.stdscr = stdscr
self.run_forever()
def run_forever(self):
# 用不阻塞方式监听键盘的输入
self.stdscr.nodelay(True)
while True:
# 如果未监听到键盘输入,会返回curses.ERR
# 那么我们可以继续用屏蔽展示下一秒的时钟
if self.stdscr.getch() == curses.ERR:
self.tick()
time.sleep(1)
else:
# 如果监听到键盘输入,退出循环
break
def tick(self):
now = datetime.datetime.now()
strs = (
"+" * 12,
"+{}+".format(str(now.date())),
"+ {} +".format(week_days[now.weekday()]),
"+ {} +".format(now.time().strftime('%H:%M:%S')),
"+" * 12,
)
for i, s in enumerate(strs):
self.stdscr.addstr(self.y + i, self.x, s)
self.stdscr.refresh()
if __name__ == "__main__":
clock = ConsoleClock()
curses.wrapper(clock)