想练打字却找不到趁手的小工具?本文用 Python 标准库写一个打字测速器:随机出词、逐字符比对、计时计速,最后输出 WPM(每分钟单词数)和正确率。代码不到 80 行,还能顺便把 time、random、字符串处理这些基础功练扎实。

1. 游戏规则

流程很简单:程序从词库里随机挑 10 个英文单词,依次显示给你,你照着敲,敲完回车进入下一个。全部敲完后,程序根据总用时和正确字符数算出两项核心指标:

为什么要按 5 字符折算?因为 "word" 是 4 个字符,"international" 是 13 个,直接按词数算对长单词太不公平,统一折算成字符才可比。运行效果大概长这样:

第 1/10 个词: python
输入: python
[#---------] 1/10
第 2/10 个词: loop
输入: lopp
[##--------] 2/10

打字测速的原理其实就一句话:把"输入的内容"和"目标内容"逐字符比较,对得多、错得少、打得快,成绩就好。这个原理可以延伸到任何输入类场景——考试答题的自动判分、密码校验、文本比对工具,底层都是同样的逐字符比较。

2. 模块拆解

这个小工具拆成四块,每一块都是独立的函数或常量:

先看词库。把它放在模块顶部的常量里,以后想加词只改这一处:

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. 扩展想法

8. 总结与练习

这个项目虽然小,却串起了四个常用知识点:列表打乱取子集、time.time() 计时、\r 原地刷新输出、逐字符字符串比对。打字测速是"工具型小游戏"的典型代表——它没有复杂的游戏循环,但很实用,做完之后你自己每天都能用。

如果你想把它做得更专业,还可以把"评价"部分的阈值做成可配置参数,或者把每次成绩连同日期一起存档,月底翻出来看进步曲线——工具类程序的价值,就在于用得越久越有用。

练习建议:

最后给个成绩参考:一般成年人的日常打字速度在 30~50 WPM,专业打字员能到 80 WPM 以上;正确率长期低于 90% 的话,说明基础指法还有问题,建议先慢下来。把这篇的测速器留作日常工具,每周测一次,你会看到一条漂亮的进步曲线。

💡 想提高打字速度,先练正确率再练速度是公认的路径;把错词记下来专项突破,比闷头狂打有效得多。