msvcrt——控制台按键输入
本帖最后由 smallwh 于 2026-2-15 23:01 编辑msvcrt——另一种控制台输入方法
一、前言
一直以来,我们都在用 input() 从控制台获取输入,输进去的东西都会显示出来,而且每次输入完后还要按下 Enter 键
看起来没什么问题,但,你也许也有过被它折磨的痛苦吧?
好在,python给我们提供了解决方案:msvcrt!
二、介绍
msvcrt --- 来自 MS VC++ 运行时的有用例程
这些函数提供了对 Windows 平台上一些有用功能的访问。一些更高级别的模块使用这些函数来构建其服务的 Windows 实现。 例如, getpass 模块在实现 getpass() 函数时使用了这些函数。——python 官方文档
首先,这是一个仅限于 Windows 平台的模块,其他平台的鱼油只能先眼馋一会儿了{:10_335:}
其次,用于控制台的函数在 IDLE 中不能正常发挥功能,所以我们得用 cmd 运行相关程序
该模块的一些函数及常量分为以下三类:
文件操作locking(fd, mode, nbytes) setmode(fd, flags) open_osfhandle(handle, flags) get_osfhandle(fd)
LK_LOCK LK_RLCKLK_NBLCK LK_NBRLCK LK_UNLCK
控制台 I/Okbhit() getch() getwch() getche() getwche()
putch(char) putwch(unicode_char) ungetch(char) ungetwch(unicode_char)
其他函数heapmin()
三、进一步深入
1. 文件操作
不是本教程重点,这里直接给官方文档(作者表示没用过,文档表述有些让人头大 {:10_245:})
[*]msvcrt.locking(fd, mode, nbytes)
基于文件描述符 fd 从 C 运行时锁定文件的某一部分。 失败时引发 OSError。 锁定的文件区域从当前文件位置开始扩展 nbytes 个字节,并可能持续到超出文件末尾。 mode 必须为下面列出的 LK_* 之一。 一个文件中的多个区域可以被同时锁定,但是不能重叠。 相邻区域不会被合并;它们必须单独被解锁。
引发一个 审计事件 msvcrt.locking,附带参数 fd, mode, nbytes。
[*]msvcrt.LK_LOCK
msvcrt.LK_RLCK
[*]锁定指定的字节数据。 如果字节数据无法被锁定,程序会在 1 秒之后立即重试。 如果在 10 次尝试后字节数据仍无法被锁定,则会引发 OSError。
[*]msvcrt.LK_NBLCK
msvcrt.LK_NBRLCK
[*]锁定指定的字节数据。 如果字节数据无法被锁定,则会引发 OSError。
[*]msvcrt.LK_UNLCK
解锁指定的字节数据,该对象必须在之前被锁定。
[*]msvcrt.setmode(fd, flags)
设置文件描述符 fd 的行结束符转写模式。 要将其设为文本模式,则 flags 应当为 os.O_TEXT;设为二进制模式,则应当为 os.O_BINARY。
[*]msvcrt.open_osfhandle(handle, flags)
基于文件句柄 handle 创建一个 C 运行时文件描述符。 flags 形参应当 os.O_APPEND, os.O_RDONLY 和 os.O_TEXT 按位 OR 的结果。 返回的文件描述符可以被用作 os.fdopen() 的形参以创建一个文件对象。
引发一个 审计事件 msvcrt.open_osfhandle,附带参数 handle, flags。
[*]msvcrt.get_osfhandle(fd)
返回文件描述符 fd 的文件句柄。 如果 fd 不能被识别则会引发 OSError。
引发一个 审计事件 msvcrt.get_osfhandle,附带参数 fd。
据AI说:文件锁定 (locking) 是并发编程中防止数据竞争的重要工具。例如,在多个进程可能会同时写入同一个文件的情况下,使用文件锁可以确保某一时刻只有一个进程有权限写入,从而保护数据的完整性。模式转换 (setmode) 是避免“二进制文件损坏”问题的关键。如果你在Windows上用二进制模式("rb"或"wb")打开文件,但底层文件描述符错误地设置为文本模式,就可能导致数据被意外修改。setmode允许你对此进行精确控制。
static/image/hrline/4.gifstatic/image/hrline/4.gif
2. heapmin()
一般没啥用的释放内存函数。尝试将 python 占用的内存归还给系统。
[*]msvcrt.heapmin()
强制 malloc() 堆清空自身并将未使用的块返回给操作系统。 失败时,这将引发 OSError。
static/image/hrline/4.gifstatic/image/hrline/4.gif
3.控制台 I/O
划重点!下面函数一些特性弥补了 input() 的不足。
[*]msvcrt.kbhit()
如果有某个按键正在等待被读取则返回 True。
(中译中:可以用它避免没有输入时一直等待。但是有一段反应时间,如果需要读取多次时,第二次要等待零点几秒才会返回 True)
[*]msvcrt.getch()
读取一个按键并将结果字符返回为一个字节串。 不会有内容回显到控制台。 如果还没有任何键被按下此调用将会阻塞,但它将不会等待 Enter 被按下。 如果按下的键是一个特殊功能键,此函数将返回 '\000' 或 '\xe0';下一次调用将返回键代码。 Control-C 按钮无法使用此函数来读取。
(翻译:没有输入时会等待输入,有输入时返回 bytes 对象,不会有输入的内容出现在屏幕上。#对,就是那个 decode 后变成 str 的 bytes 对象。
特殊功能键如 F1、delete 会返回 b'\000' 或 b'\xe0',此时还有一个键等待读取,再读取一次就能确定按的是什么键。# b'\xe0' 等使用 UTF-8 解码会报错
本函数对中文支持较差,中文也需要读取多次。)
[*]msvcrt.getwch()
getch() 的宽字符版本,返回一个 Unicode 值。
(翻译:没有输入时会等待输入,有输入时返回 str 字符串,不会有输入的内容出现在屏幕上。
特殊功能键也需要读取两次,但是返回值有些奇怪。这个函数较好地支持中文~)
[*]msvcrt.getche()
类似于 getch(),但按键如果表示一个可打印字符则它将被回显。
(翻译:输入的内容会尽量出现在屏幕上。其他同getch())
[*]msvcrt.getwche()
getche() 的宽字符版本,返回一个 Unicode 值。
(翻译:输入的内容会尽量出现在屏幕上。其他同getwch())
[*]msvcrt.putch(char)
将字符串 char 打印到终端,不使用缓冲区。
(翻译:输出一个长度为 1 的 bytes,如 b'D')
[*]msvcrt.putwch(unicode_char)
putch() 的宽字符版本,接受一个 Unicode 值。
(翻译:输出一个长度为 1 的字符串。这个函数较好地支持中文,如'你')
[*]msvcrt.ungetch(char)
使得字节串 char 被“推回”终端缓冲区;它将是被 getch() 或 getche() 读取的下一个字符。
(翻译:传入一个长度为 1 的 bytes,如 b'D'。管他用户输入了什么,下一次读取输入就是 b'D' 了!)
[*]msvcrt.ungetwch(unicode_char)
ungetch() 的宽字符版本,接受一个 Unicode 值。
(翻译:传入一个长度为 1 的字符串 str ,如'中'。管他用户输入了什么,下一次读取输入就是 '中' 了!)
plus: 1.如果你一次性用输入法输入了多个字符,那么也需要对应读取多次。
2.很多功能都与缓冲区有关,意味着你的输入会一直保存到下一次合适的读取。如果一次不读取所有输入,结果可能会让人迷惑。
3. 该模块实现了控制台 I/O API 的普通和宽字符变体。普通的 API 只处理ASCII字符,国际化应用受限。应该尽可能地使用宽字符 API 。
——python官方文档
getwch 等函数能更好地减少需要读取两次的情况,所以,名字里带 w 的函数才是正道。
现在,抓紧运行下面的程序,然后对键盘狂按一番吧:
import msvcrt
import time
while True:
print(f'使用msvcrt.getch读取到按键{msvcrt.getch()}',flush=True)#换成其他函数试一试!
time.sleep(0.3)#为什么要等待一会儿?
while msvcrt.kbhit():
print(f'\a继续捕获到按键{msvcrt.getch()}',flush=True)
time.sleep(0.3)
四、应用
1. python 的 getpass 模块用到了这一模块:
def win_getpass(prompt='Password: ', stream=None):
"""Prompt for password with echo off, using Windows getch()."""
if sys.stdin is not sys.__stdin__:
return fallback_getpass(prompt, stream)
for c in prompt:
msvcrt.putwch(c)
pw = ""
while 1:
c = msvcrt.getwch()
if c == '\r' or c == '\n':
break
if c == '\003':
raise KeyboardInterrupt
if c == '\b':
pw = pw[:-1]
else:
pw = pw + c
msvcrt.putwch('\r')
msvcrt.putwch('\n')
return pw
2. 一个简易的根据拼音(或首字母)统计学生姓名的程序,支持自动补全:
import msvcrt
from time import strftime
from os import startfile
BACKSPACE = '\x08'
CTRLZ = '\x1a'
ESC = '\x1b'
FUNCTION = '\x08\x1a'
ALLSPECIAL = '\x08\x1a\x1b'
namefile = open('.\\名字表.txt')
info = dict(i.split() for i in namefile)
namefile.close()
all_students = set(info.keys())
all_pinyin = ' ' + ' '.join(all_students) + ' '
handed_students = []
def echo(ch,lenth):
if ch == CTRLZ and not lenth:
print('←',flush=True,end='')
elif ch == BACKSPACE and lenth:
print('\b \b',flush=True,end='')
elif ch not in ALLSPECIAL:
print(ch,flush=True,end='')
def update(ch,characters):
if ch == BACKSPACE:
characters = characters[:-1]
elif ch not in FUNCTION or not characters:
characters += ch
return characters
def autocomplete(characters):
left = all_pinyin.find(' '+characters) + 1
right = all_pinyin.find(' ',left)
return all_pinyin
def getinput():
characters = ''
while True:
ch = msvcrt.getwch()
if ch == '\r':
break
echo(ch,characters)
characters = update(ch,characters)
times = all_pinyin.count(' '+characters)
if len(characters) > 1 and times == 1:
characters = autocomplete(characters)
break
elif times == 0:
break
return characters
#-----------------------------------------#
print('''欢迎使用学生统计程序!author:smallwh
Ctrl+Z撤销 Esc键结束 退格键删除字母
''')
while True:
index = len(handed_students) + 1
print(f'{index}:',end='',flush=True)
pinyin = getinput()
print('\t\a',end='')
if pinyin == ESC:
break
elif pinyin == CTRLZ:
if index > 1:
print(f'已撤消录入{handed_students.pop()}')
else:
print('尚未录入!')
elif pinyin not in all_students:
print(f'名字{pinyin}不存在!')
elif pinyin in handed_students:
print(f'名字{pinyin}输入重复!')
else:
handed_students.append(pinyin)
handed_names = (info for i in handed_students)
not_handed_students = all_students - set(handed_students)
not_handed_names = (info for i in not_handed_students)
f = open('.\\作业情况.txt','a+')
print(strftime("%Y/%m/%d %X"),file=f)
print(f'一共出现{index-1}人,',*handed_names,file=f)
print('未出现:',*not_handed_names,file=f,end='\n\n')
f.close()
startfile('.\\作业情况.txt')
需要提前准备名字表.txt,置于同一目录下。
示例:
zs 张三
ls 李四
ww 王五
zhangsi 张四
3.控制台中的 2048 游戏
**** Hidden Message *****
结语
msvcrt 模块介绍到此结束
大家可以继续补充这个模块的相关内容,或者进一步完善这些代码(自己写的,见笑)。
如果发现有什么错误,请及时指出
还有,希望能有一些评分。
最后,祝大家新春快乐! 非常棒!! 申精#文章格式建议【官方指导】
https://fishc.com.cn/thread-146275-1-1.html
(出处: 鱼C论坛)
页:
[1]