AI编程(二十):单位转换器 — 一个字典搞定所有换算

作者:

在

做饭要换算克和毫升,出差要换算时区——让字典和函数帮你记住所有公式

一、你一定经历过这种时刻

做饭看菜谱:”面粉200克,牛奶150毫升”。你家只有量杯,怎么量?

出差订机票:航班显示”当地时间14:30起飞”,北京时间是几点?

健身看教程:”做3组,每组15磅的哑铃”。磅是多少斤?

单位换算这事儿,特点是每个都会,但每次都得现查。1千米等于多少英里?查一下。1磅等于多少克?查一下。查多了就烦。

今天我们做一个单位转换器:所有换算公式存在一个字典里,程序帮你算。以后忘了公式不用查,跑一下自己的程序就行。

这个项目会重点用到:字典(存转换公式)、函数(每个换算逻辑打包)、循环(反复换算)、条件判断(不同类别不同算法)、try/except(输入验证)。

二、换算的本质:找一个”中间人”

换算怎么算?诀窍是找一个基准单位,所有单位都先跟它挂钩。

以长度为例,我们选米做基准:

  • 1千米 = 1000米
  • 1厘米 = 0.01米
  • 1英尺 = 0.3048米

那”3千米等于多少厘米”怎么算?

  1. 3千米 → 米:3 × 1000 = 3000米
  2. 3000米 → 厘米:3000 ÷ 0.01 = 300000厘米

两步走:先换成基准,再从基准换出去。 这样任意两个单位之间都能互转,不用记n×n个公式,每个单位只需要记”它和基准的关系”——一个数字。

这个数字,就存进字典。

三、先跟AI聊

你的提示词:

用Python写一个单位转换器,命令行交互,中文界面。功能:输入一个数值和”从什么单位”,再输入”换成什么单位”,程序输出换算结果。先只支持长度单位:米、千米、厘米、毫米、英尺、英寸、英里。用字典存储各单位到米的转换系数,用函数实现换算逻辑,用循环支持反复换算,输入非法时用try/except处理不崩溃。加详细的中文注释。

四、基础版:长度转换器

# ========== 转换数据:所有单位和"米"的关系 ==========
# 字典的值是"1个该单位等于多少米"
长度系数 = {
    "米": 1,
    "千米": 1000,
    "厘米": 0.01,
    "毫米": 0.001,
    "英尺": 0.3048,
    "英寸": 0.0254,
    "英里": 1609.34
}

def 转换长度(数值, 来单位, 去单位):
    """把数值从一个长度单位换到另一个"""
    # 第一步:换算成基准单位"米"
    米数 = 数值 * 长度系数[来单位]
    # 第二步:从基准单位换到目标单位
    结果 = 米数 / 长度系数[去单位]
    return 结果

print("=== 长度转换器 ===")
print("支持的单位:", "、".join(长度系数.keys()))

while True:
    print("-" * 30)
    # 输入数值,用try/except防止崩溃
    try:
        数值 = float(input("请输入数值(输入q退出):"))
    except ValueError:
        # 用户输入了q或乱七八糟的东西
        print("👋 再见!")
        break

    来单位 = input("从什么单位:").strip()
    去单位 = input("换成什么单位:").strip()

    # 检查单位是否存在
    if 来单位 not in 长度系数 or 去单位 not in 长度系数:
        print("❌ 不认识这个单位,请检查拼写")
        continue

    结果 = 转换长度(数值, 来单位, 去单位)
    print(f"✅ {数值} {来单位} = {结果:.4f} {去单位}")

五、逐行理解

5.1 系数字典:一本不用翻的公式手册

长度系数 = {
    "米": 1,
    "千米": 1000,
    "厘米": 0.01,
    ...
}

每个键是一个单位名,每个值是”1个这个单位等于多少米”。这本”公式手册”只有一页,但能查所有长度单位——因为基准统一了。

想加新单位?比如海里:往字典里加一行 "海里": 1852,其他代码一个字都不用改。这就是字典的威力:数据加一项,功能自动扩展。

5.2 转换函数:两步走

def 转换长度(数值, 来单位, 去单位):
    米数 = 数值 * 长度系数[来单位]
    结果 = 米数 / 长度系数[去单位]
    return 结果
  • 入参:数值、来源单位、目标单位
  • 第一步:乘以来源单位的系数,变成米
  • 第二步:除以目标单位的系数,变成目标单位
  • 返回:换算结果

“英里换千米”:数值 × 1609.34 ÷ 1000。对不对?1英里=1.60934千米,对的。

5.3 输入验证:两手准备

try:
    数值 = float(input("请输入数值(输入q退出):"))
except ValueError:
    print("👋 再见!")
    break

有意思的是这里的一石二鸟:用户输入”q”,float("q")会抛ValueError,正好用它当退出信号。输入”abc”之类的垃圾也走这条分支——虽然提示语是”再见”,但程序至少不会崩溃(想更严谨可以再判断一下输入是不是”q”,练习里做)。

if 来单位 not in 长度系数 or 去单位 not in 长度系数:
    print("❌ 不认识这个单位,请检查拼写")
    continue

单位名是文字,不存在float转换的问题,但可能拼错。in检查字典里有没有这个键,没有就提醒重来。or表示”两个里有一个不认识就不干”。

5.4 格式化输出

print(f"✅ {数值} {来单位} = {结果:.4f} {去单位}")

{结果:.4f}表示保留4位小数。不加这个的话,1英里换千米可能显示1.6093399999999999——浮点数的老毛病,第十三篇提过。保留4位,清爽多了。

六、运行试试

python 单位转换器.py
请输入数值(输入q退出):3
从什么单位:千米
换成什么单位:英里
✅ 3.0 千米 = 1.8641 英里

多试几个:5英尺是多少厘米?(152.4)1英里是多少毫米?(1609340)

基础版能用了,但只有长度一种类别。做饭的克和毫升呢?出差的时区呢?

七、第一个改进:支持多个类别

核心思路:系数字典升级成”两层”——外面一层按类别,里面一层是单位系数。

# ========== 所有换算数据:按类别组织 ==========
转换数据 = {
    "长度": {           # 基准:米
        "米": 1,
        "千米": 1000,
        "厘米": 0.01,
        "毫米": 0.001,
        "英尺": 0.3048,
        "英寸": 0.0254,
        "英里": 1609.34
    },
    "重量": {           # 基准:克
        "克": 1,
        "千克": 1000,
        "毫克": 0.001,
        "斤": 500,
        "两": 50,
        "磅": 453.592,
        "盎司": 28.3495
    },
    "体积": {           # 基准:毫升
        "毫升": 1,
        "升": 1000,
        "立方米": 1000000,
        "加仑": 3785.41,
        "杯": 236.588
    },
    "时间": {           # 基准:秒
        "秒": 1,
        "分钟": 60,
        "小时": 3600,
        "天": 86400,
        "周": 604800
    }
}

def 转换(类别, 数值, 来单位, 去单位):
    """通用换算函数:先选类别,再按基准换算"""
    系数表 = 转换数据[类别]       # 取出该类别的系数字典
    基准值 = 数值 * 系数表[来单位]  # 第一步:换成基准
    return 基准值 / 系数表[去单位]   # 第二步:换成目标

函数只写了一个,但长度、重量、体积、时间全能换。因为换算逻辑本来就一样(乘来除去),变的只是查哪本”手册”。函数多加一个类别参数,让它知道去哪本手册里查。

主循环改成先选类别:

print("=== 万能单位转换器 ===")

while True:
    print("n" + "=" * 30)
    print("支持的类别:")
    类别列表 = list(转换数据.keys())
    for i, 名称 in enumerate(类别列表, 1):    # enumerate从1开始编号
        单位们 = "、".join(转换数据[名称].keys())
        print(f"  {i}. {名称}({单位们})")
    print("  0. 退出")

    选择 = input("选择类别(输入数字):").strip()
    if 选择 == "0":
        print("👋 再见!")
        break

    try:
        类别 = 类别列表[int(选择) - 1]    # 数字转成类别名
    except (ValueError, IndexError):
        print("❌ 无效选择")
        continue

    系数表 = 转换数据[类别]

    try:
        数值 = float(input("请输入数值:"))
    except ValueError:
        print("❌ 请输入数字")
        continue

    来单位 = input(f"从什么单位({'/'.join(系数表.keys())}):").strip()
    去单位 = input("换成什么单位:").strip()

    if 来单位 not in 系数表 or 去单位 not in 系数表:
        print(f"❌ 【{类别}】类别里没有这个单位")
        continue

    结果 = 转换(类别, 数值, 来单位, 去单位)
    print(f"✅ {数值} {来单位} = {结果:.4f} {去单位}")

几个值得注意的细节:

  • enumerate(类别列表, 1):第六篇学过的循环,加上编号。1表示从1开始数。
  • except (ValueError, IndexError):两种错误一起接——输入非数字是ValueError,输入超范围数字(比如99)是IndexError。元组里可以放多个错误类型。
  • 类别列表[int(选择) - 1]:用户输1取第0个元素,输2取第1个……列表下标从0开始,所以减1。第四篇讲过的事。

温度怎么办? 有个坑:摄氏度和华氏度之间不是”乘个系数”的关系(0°C = 32°F,不是0),用系数字典搞不定。处理方式有两个——要么给温度单独写个函数用公式算,要么聪明一点:在转换函数里加个条件判断:

def 转换(类别, 数值, 来单位, 去单位):
    # 温度特殊处理:不能用系数法,要用公式
    if 类别 == "温度":
        return 转换温度(数值, 来单位, 去单位)
    系数表 = 转换数据[类别]
    return 数值 * 系数表[来单位] / 系数表[去单位]

def 转换温度(数值, 来单位, 去单位):
    """温度换算:先把各种温度都转成摄氏度,再转出去"""
    if 来单位 == "摄氏度":
        摄氏 = 数值
    elif 来单位 == "华氏度":
        摄氏 = (数值 - 32) * 5 / 9
    else:    # 开尔文
        摄氏 = 数值 - 273.15

    if 去单位 == "摄氏度":
        return 摄氏
    elif 去单位 == "华氏度":
        return 摄氏 * 9 / 5 + 32
    else:
        return 摄氏 + 273.15

看到了吗?温度的处理套路和长度一模一样——先全部转成一个基准(摄氏度),再从基准转出去。区别只是”转成基准”不能靠乘系数,得靠公式,所以要用条件判断。

“先统一成基准,再转出去”这个思想,贯穿了整个程序。 这就是设计模式的雏形。

八、第二个改进:批量转换

做菜时经常要一次换算好几种材料。加个批量模式:

def 批量转换(类别):
    """一次输入多行数据,全部换算"""
    系数表 = 转换数据[类别]
    print(f"n支持的单位:{'、'.join(系数表.keys())}")
    print("每行输入:数值 来单位 去单位(如:200 克 千克)")
    print("输入done结束n")

    while True:
        行 = input("> ").strip()
        if 行 == "done":
            break

        # 把一行拆成三部分
        部分 = 行.split()        # 按空格拆分字符串
        if len(部分) != 3:
            print("  ⚠️ 格式不对,示例:200 克 千克")
            continue

        数值文字, 来单位, 去单位 = 部分    # 元组解包

        # 验证
        try:
            数值 = float(数值文字)
        except ValueError:
            print("  ⚠️ 第一项不是数字")
            continue
        if 来单位 not in 系数表 or 去单位 not in 系数表:
            print(f"  ⚠️ 单位不存在")
            continue

        结果 = 转换(类别, 数值, 来单位, 去单位)
        print(f"  ✅ {数值} {来单位} = {结果:.4f} {去单位}")

新东西只有一个:split()——把字符串按空格拆成列表。

"200 克 千克".split()   →   ["200", "克", "千克"]

字符串处理是第十五篇单词统计器练过的,split()是它的老搭档。拆完之后逐个验证,全合法就算出结果。

效果:

> 200 克 千克
  ✅ 200.0 克 = 0.2000 千克
> 3 千米 英里
  ✅ 3.0 千米 = 1.8641 英里
> 5 英尺 厘米
  ✅ 5.0 英尺 = 152.4000 厘米
> done

一行一个换算,做完一起看。比反复走菜单流程快多了。

九、第三个改进:自定义转换率

总有些冷门单位程序里没有:你奶奶口中的”几钱”、你健身教练用的”普特”、你工作里的”标准箱”。与其求我加,不如让程序自己学会:

def 添加自定义单位():
    """让用户自己往类别里加单位"""
    print(f"n可选类别:{'、'.join(转换数据.keys())}")
    类别 = input("要往哪个类别添加单位:").strip()

    if 类别 not in 转换数据:
        print("❌ 没有这个类别")
        return

    名称 = input("新单位名称(如:标准箱):").strip()
    if not 名称:
        print("❌ 名称不能为空")
        return

    系数表 = 转换数据[类别]
    基准名 = list(系数表.keys())[0]     # 每个类别的第一个就是基准单位

    try:
        系数 = float(input(f"1个{名称}等于多少{基准名}:"))
    except ValueError:
        print("❌ 系数必须是数字")
        return
    if 系数 <= 0:
        print("❌ 系数必须大于0")
        return

    系数表[名称] = 系数                # 往字典里加一项,功能就有了!
    print(f"✅ 已添加:1{名称} = {系数}{基准名}")
    print(f"   现在{类别}类别支持:{'、'.join(系数表.keys())}")

就这么几行,用户体验完全不同了:

新单位名称(如:标准箱):普特
1个普特等于多少克:16380
✅ 已添加:1普特 = 16380克

加完立刻能用:1普特换多少斤?32.76斤。

程序从”我给你什么功能”变成了”你教我什么功能”。 而实现方式简单得可笑:往字典里加一对键值。

注意运行期间加的单位只存在内存里,程序一关就没了。想永久保存?你已经会了——第九篇的文件操作,用json.dump把转换数据存起来,下次启动时json.load读回来。这就是今日小练习之一。

十、完整代码骨架

把所有部分合起来,程序结构是:

万能单位转换器
├── 数据层:转换数据(四层嵌套字典:类别→单位→系数)
├── 功能层:
│   ├── 转换() —— 通用换算(先转基准,再转目标)
│   ├── 转换温度() —— 温度特殊公式
│   ├── 批量转换() —— 一次换算多行
│   └── 添加自定义单位() —— 用户扩展单位
└── 主循环:选类别 → 输数值 → 选单位 → 输出结果

数据、功能、交互,三层分开。这是第十二篇实战项目就建立的结构感,现在越来越熟练了。

十一、回顾一下用到了什么

知识点 在哪里用的

|——–|———–|

字典 转换系数表
嵌套字典 类别→单位→系数 三层结构
字典.keys() 显示支持的单位
动态添加键值 自定义单位
函数 转换、批量转换、添加单位
函数参数 类别、数值、来源、目标
return 返回换算结果
条件判断 温度特殊处理、单位验证
循环 主循环、批量输入循环
break/continue 退出/跳过非法输入
try/except 数值输入、多错误类型一起捕获
列表 类别列表、split()的结果
enumerate 菜单编号
元组解包 拆分批量输入
split() 一行输入拆成三部分
len() 检查输入格式
f-string格式化 保留4位小数
join() 拼接单位名显示

十二、今日小练习

三个练习,从易到难:

练习1:更聪明的退出

现在的退出逻辑有点粗暴——输错数字也说”再见”。改成:先用input()拿到原始文字,判断是不是”q”,是就退出;不是再用try/except转数字。这样输”abc”会提示”请输入数字”,输”q”才退出。

提示:

文字 = input("请输入数值(输入q退出):").strip()
if 文字.lower() == "q":
    break
try:
    数值 = float(文字)
except ValueError:
    print("❌ 请输入数字")
    continue

练习2:保存自定义单位

让自定义单位在程序重启后依然有效。提示:

  • 添加单位成功后,把整个转换数据用json.dump存到文件
  • 程序启动时先尝试用json.load读取,读到就覆盖转换数据
  • 别忘了try/except FileNotFoundError

练习3:反向查询模式

加一个”查询模式”:输入”1英里等于多少”(不指定目标单位),程序把该类别下所有其他单位的结果都列出来。提示:用for 单位 in 系数表:遍历,跳过来源单位自己。


下一篇:倒计时提醒器 — 煮面计时5分钟、开会前10分钟提醒。让程序帮你盯着时间,用上time模块——全系列唯一的新东西,但你5分钟就能学会。

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注