想练打字却找不到趁手的小工具?本文用 Python 标准库写一个打字测速器:随机出词、逐字符比对、计时计速,最后输出 WPM(每分钟单词数)和正确率。代码不到 80 行,还能顺便把 time、random、字符串处理这些基础功练扎实。
1. 游戏规则
流程很简单:程序从词库里随机挑 10 个英文单词,依次显示给你,你照着敲,敲完回车进入下一个。全部敲完后,程序根据总用时和正确字符数算出两项核心指标:
- WPM(Words Per Minute):每分钟打对多少个"标准单词"。行业惯例是一个单词按 5 个字符折算,所以
WPM = 正确字符数 ÷ 5 ÷ 分钟数 - 正确率:正确字符占"应打字符 + 多打字符"的百分比
为什么要按 5 字符折算?因为 "word" 是 4 个字符,"international" 是 13 个,直接按词数算对长单词太不公平,统一折算成字符才可比。运行效果大概长这样:
第 1/10 个词: python
输入: python
[#---------] 1/10
第 2/10 个词: loop
输入: lopp
[##--------] 2/10
打字测速的原理其实就一句话:把"输入的内容"和"目标内容"逐字符比较,对得多、错得少、打得快,成绩就好。这个原理可以延伸到任何输入类场景——考试答题的自动判分、密码校验、文本比对工具,底层都是同样的逐字符比较。
2. 模块拆解
这个小工具拆成四块,每一块都是独立的函数或常量:
- 词库与抽词:一个单词列表,用
random.shuffle打乱后取前 N 个 - 计时:
time.time()记录开始和结束时刻,相减得总用时 - 比对:逐字符比较目标词和你的输入,统计对错
- 统计输出:套公式算 WPM 和正确率,打印成绩单
先看词库。把它放在模块顶部的常量里,以后想加词只改这一处:
import random
WORDS = [
'python', 'function', 'variable', 'loop', 'dictionary',
'algorithm', 'compile', 'debug', 'module', 'recursion',
'terminal', 'keyboard', 'practice', 'speed', 'accuracy',
'language', 'console', 'integer', 'string', 'import',
'return', 'while', 'break', 'error', 'output',
]
TOTAL = 10 # 本轮要打的单词数
random.shuffle(WORDS)
print(WORDS[:TOTAL])
词库选的都是编程和日常打字的高频词,长度适中。元组里的逗号、括号这些标点先不引入,等基础版跑通了再加难度。
词库还有一个隐藏作用:它是天然的"难度调节器"。全是短词,一轮打下来正确率会很高,适合新手建立信心;混入长词和生僻词,错误率立刻上升。你完全可以把词库替换成自己工作中常用的英文术语,让练习变得更有针对性。
3. 逐步实现:抽词与进度条
import random
import sys
WORDS = ['python', 'loop', 'debug', 'string', 'import',
'error', 'while', 'break', 'speed', 'accuracy']
TOTAL = 10
def pick_words():
random.shuffle(WORDS)
return WORDS[:TOTAL]
def show_progress(done, total):
bar = '#' * done + '-' * (total - done)
sys.stdout.write(f'\r[{bar}] {done}/{total}')
sys.stdout.flush()
words = pick_words()
print('本轮单词:', words)
show_progress(3, TOTAL)
print()
random.shuffle 直接原地打乱列表,取前 TOTAL 个保证一轮内不重复。进度条用 \r(回车符)回到行首,配合 sys.stdout.write 不换行输出,就能在同一行原地刷新——这是终端小工具的常用技巧。print 默认会追加换行,所以这里必须绕开它,最后记得 flush(),否则内容会积在缓冲区里不显示。
进度条虽然只是锦上添花,但它示范了一个重要的终端编程思想:任何"持续变化的信息"都适合原地刷新——下载进度、日志计数、计时器,原理都是"写回车符回到行首、重写整行"。学会了这个,你的终端程序质感会提升一大截。
4. 核心算法:逐字符比对与计时
import time
def compare(target, typed):
"""逐字符比对,返回 (正确数, 错误数)"""
correct = wrong = 0
for i in range(max(len(target), len(typed))):
if i < len(target) and i < len(typed) and target[i] == typed[i]:
correct += 1
else:
wrong += 1
return correct, wrong
print(compare('python', 'pythn')) # 漏了一个 o
print(compare('python', 'pythonx')) # 多打了一个 x
start = time.time()
time.sleep(0.05) # 模拟打字耗时
print(f'耗时 {time.time() - start:.2f} 秒')
compare 用 max(len(target), len(typed)) 决定循环次数:输入比目标短,漏掉的字符计错;输入比目标长,多出来的字符也计错——"漏打"和"多打"都被公平对待。计时用 time.time(),它返回自 1970 年以来的秒数(浮点),开始和结束各取一次,相减就是总用时,这是最朴素也最可靠的计时方式。
逐字符比对还有一个好处:它天然支持"部分正确"——打错一个字母只扣这一个,其余照常得分。这比"整词对错"的判定公平得多,也更接近真实打字的感觉:现实中我们打错一个字母,很少会重打整个单词。
5. 统计与成绩单
主循环里把每个词的正确数、错误数累加起来,结束后统一算成绩:
correct_chars, wrong_chars = 48, 7 # 假设的累计结果
elapsed = 42.5 # 假设的总用时(秒)
minutes = elapsed / 60
wpm = (correct_chars / 5) / minutes if minutes > 0 else 0
accuracy = correct_chars / (correct_chars + wrong_chars) * 100
print(f'用时: {elapsed:.1f} 秒')
print(f'速度: {wpm:.0f} WPM')
print(f'正确率: {accuracy:.1f}%')
WPM 的公式是"正确字符数 ÷ 5 ÷ 分钟数",因为 5 字符 = 1 标准词。注意 minutes > 0 的防御:万一用时为 0(几乎不可能),除以 0 会直接抛 ZeroDivisionError。.1f 和 .0f 是格式化字符串的精度控制,一个保留 1 位小数,一个取整。
这里再提醒一个细节:成绩单里的 正确字符 分母用的是 total_chars(所有目标词的长度之和),而不是你实际输入的字符数——因为"少打"和"多打"都会反映在正确率里,分母必须固定,成绩才有可比性。
6. 完整代码
把上面几段拼起来就是完整程序,保存为 typing_test.py,运行 python typing_test.py 即可:
import random
import sys
import time
WORDS = [
'python', 'function', 'variable', 'loop', 'dictionary',
'algorithm', 'compile', 'debug', 'module', 'recursion',
'terminal', 'keyboard', 'practice', 'speed', 'accuracy',
'language', 'console', 'integer', 'string', 'import',
'return', 'while', 'break', 'error', 'output',
]
TOTAL = 10
def pick_words():
random.shuffle(WORDS)
return WORDS[:TOTAL]
def show_progress(done, total):
bar = '#' * done + '-' * (total - done)
sys.stdout.write(f'\r[{bar}] {done}/{total}')
sys.stdout.flush()
def compare(target, typed):
correct = wrong = 0
for i in range(max(len(target), len(typed))):
if i < len(target) and i < len(typed) and target[i] == typed[i]:
correct += 1
else:
wrong += 1
return correct, wrong
def main():
targets = pick_words()
total_chars = sum(len(w) for w in targets)
correct_chars = wrong_chars = 0
start = time.time()
for i, word in enumerate(targets, 1):
show_progress(i - 1, TOTAL)
print(f'\n第 {i}/{TOTAL} 个词:{word}')
try:
typed = input('输入: ').strip()
except EOFError:
break
c, w = compare(word, typed)
correct_chars += c
wrong_chars += w
elapsed = time.time() - start
minutes = elapsed / 60
wpm = (correct_chars / 5) / minutes if minutes > 0 else 0
tried = correct_chars + wrong_chars
accuracy = correct_chars * 100 / tried if tried else 0
print('\n' + '=' * 20)
print(f'用时: {elapsed:.1f} 秒')
print(f'正确字符: {correct_chars}/{total_chars}')
print(f'速度: {wpm:.0f} WPM')
print(f'正确率: {accuracy:.1f}%')
if accuracy >= 95 and wpm >= 30:
print('评价:非常棒!')
elif accuracy < 80:
print('评价:别急,先求准再求快')
else:
print('评价:不错,继续练习!')
if __name__ == '__main__':
main()
整个程序只有 70 多行,没有一行是多余的:enumerate(targets, 1) 同时拿到序号和单词;strip() 去掉玩家输入时不小心带上的空格;按 Ctrl+D 提前结束时,EOFError 被捕获并跳出循环,成绩照常结算——毕竟测速被打断也算一次练习。正确率的分母用 tried 变量存起来再判断,防止一行都没打就结算时除零。
如果觉得 10 个词太少,把 TOTAL 改成 20 或 30 即可;觉得英文没意思,把 WORDS 整块换成中文词组(比如"编程、学习、算法、游戏"),程序不用改一行——不过中文要靠输入法上屏,计时会混入选词时间,这个取舍留给你自己体会。
7. 扩展想法
- 难度分级:准备简单 / 中等 / 困难三份词库,按单词长度分档,开局让玩家选
- 限时模式:不限词数,只限 60 秒,用
time.time()判断超时后自动结算,更有紧迫感 - 错词本:把打错的词收集进列表,下一轮优先重测,针对薄弱点练习
- 历史纪录:把每次成绩追加写入
records.txt,用max()找出历史最好成绩
8. 总结与练习
这个项目虽然小,却串起了四个常用知识点:列表打乱取子集、time.time() 计时、\r 原地刷新输出、逐字符字符串比对。打字测速是"工具型小游戏"的典型代表——它没有复杂的游戏循环,但很实用,做完之后你自己每天都能用。
如果你想把它做得更专业,还可以把"评价"部分的阈值做成可配置参数,或者把每次成绩连同日期一起存档,月底翻出来看进步曲线——工具类程序的价值,就在于用得越久越有用。
练习建议:
- 给
compare加"忽略大小写"选项,试试先typed.lower()再比较 - 把词库换成中文词组(如"编程、学习、游戏"),思考中文输入法的计时和比对该怎么处理
- 统计每个词单独的正确率,找出你最容易打错的单词,做成"重点词库"
最后给个成绩参考:一般成年人的日常打字速度在 30~50 WPM,专业打字员能到 80 WPM 以上;正确率长期低于 90% 的话,说明基础指法还有问题,建议先慢下来。把这篇的测速器留作日常工具,每周测一次,你会看到一条漂亮的进步曲线。
💡 想提高打字速度,先练正确率再练速度是公认的路径;把错词记下来专项突破,比闷头狂打有效得多。