做饭要换算克和毫升,出差要换算时区——让字典和函数帮你记住所有公式
一、你一定经历过这种时刻
做饭看菜谱:”面粉200克,牛奶150毫升”。你家只有量杯,怎么量?
出差订机票:航班显示”当地时间14:30起飞”,北京时间是几点?
健身看教程:”做3组,每组15磅的哑铃”。磅是多少斤?
单位换算这事儿,特点是每个都会,但每次都得现查。1千米等于多少英里?查一下。1磅等于多少克?查一下。查多了就烦。
今天我们做一个单位转换器:所有换算公式存在一个字典里,程序帮你算。以后忘了公式不用查,跑一下自己的程序就行。
这个项目会重点用到:字典(存转换公式)、函数(每个换算逻辑打包)、循环(反复换算)、条件判断(不同类别不同算法)、try/except(输入验证)。
二、换算的本质:找一个”中间人”
换算怎么算?诀窍是找一个基准单位,所有单位都先跟它挂钩。
以长度为例,我们选米做基准:
- 1千米 = 1000米
- 1厘米 = 0.01米
- 1英尺 = 0.3048米
那”3千米等于多少厘米”怎么算?
- 3千米 → 米:3 × 1000 = 3000米
- 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分钟就能学会。
发表回复