如果你问一个写了好几年代码的人:“编程入门第一个项目做什么?”十有八九你会得到两个答案:Hello World,或者计算器。
我个人的态度很明确:想靠 Hello World 搞明白编程是什么,基本没戏。它只是往屏幕上扔了一行字,你连“程序是怎么处理数据的”都看不见。而“用 Python 做一个简单计算器”就不一样了——它要求你接收输入、做判断、做运算、输出结果,然后回到起点继续等待,这一整条链路已经是一个非常完整的“程序”了。
这也是 Python 精通连续剧的第一集。这一集我会带着你从零开始,把计算器从“能跑”一直做到“扛造”:能连续运算、输入错误不崩溃、代码结构清晰,并且把每一步背后的“为什么”也讲清楚。无论你是刚装好 Python 还没跑通过任何程序,还是学过几天语法但不知道能做什么,这个项目都非常适合作为你真正意义上第一个亲手写完的小项目。
1. 为什么第一集是“计算器”:一个最像“程序”的小项目
1.1 计算器里藏着编程的所有核心流程
很多初学 Python 的人会把注意力放在“语法”上:变量怎么定义、for 循环怎么写、列表和字典有什么区别。语法当然要学,但语法不是编程。编程的本质是你给计算机描述一套处理数据的流程,而计算器恰好把这个流程压缩到了最小、最直观的形态。
你回忆一下用计算器的场景:输入数字,选择运算符,输入另一个数字,按等号,看结果。换成程序语言,这就是“输入 → 处理 → 输出”。如果还想继续算,就必须有“循环”。用户可能输错,就必须有“异常处理”。这些动作加在一起,几乎构成了所有软件的核心骨架。
我再把计算器背后的知识点摊开来看,你就会发现这一集并不“小”。
| 计算器里的功能 | 对应的 Python 知识点 |
|---|---|
| 把源码保存成 .py 文件 | 脚本文件的结构、注释、缩进 |
| 从键盘读入算式 | input() 函数 |
| 把字符串变成数字 | 类型转换 float() / int() |
| 判断运算符 | if / elif / else 分支 |
| 连续计算,直到退出 | while True 循环 + break |
| 输入不合法时不让程序崩 | try / except 异常处理 |
| 把一段计算逻辑独立出来 | 自定义函数 def |
一张表格列下来你会发现:计算器项目把你以后会反复用到的几乎所有基础语法都串了一遍。更重要的是,它不是零散地“学语法”,而是把这些语法组织成一个能运行、能测试、能改进的真实程序。这种“组织感”才是初学者最缺的东西。
1.2 “Hello World”为什么喂不饱你
不是 Hello World 没用,而是它太“单薄”了。print("Hello World") 只演示了输出,而且输出的内容还是写死的。程序跑完,你除了看到一行字之外,感受不到“代码在干活”的过程。
计算器完全不同。同样的代码,不同的人输入不同的数字,得到的结果不一样。程序开始有“交互”了,它不再是一条单行道,而是一个你能和它对话的窗口。从学习心理来看,这种反馈感非常重要——你输入的 3 + 5 真的在屏幕上变成了 8,你会直观地理解“原来代码是可以影响外部世界的”。
还有一个更实际的原因:计算器能逼你处理“不确定性”。用户可能输入字母,可能把 0 当除数,可能什么都不输入直接回车,可能用中文输入法打出全角符号。这些场景在 Hello World 里面一个都不会遇到,但在真实开发里每天都会遇到。程序员的核心能力,不是写出能跑通的代码,而是写出“用户怎么折腾都不会崩”的代码。计算器项目刚好能让你在最简单的场景里练这件事。
1.3 第一集的验收标准
写代码之前先定验收标准,这是个好习惯。没有验收标准的项目,做起来永远不知道什么时候算“做完”。
我给这一集定下的标准是:
- 支持
+ - * /四种基本运算。 - 启动之后可以连续计算,直到用户主动输入
quit退出。 - 用户输入格式错误、输入非数字、除数为 0 时,程序不能崩溃,要给出清楚的提示。
- 代码里的每一行,你都能用自己的话说清楚它在干什么。
这四条标准看起来不难,但如果你能全部做到,说明你已经具备了一个非常朴素、但非常关键的技能:把需求拆解成可实现的模块,再逐块去实现。这套流程往下走很多年都不会变。
2. 环境准备:先把 Python 解释器和编辑器装明白
2.1 装 Python 时最容易错过的两个选项
这一节主要说给完全零基础的读者听,如果你已经能把python命令跑起来,可以快速跳过去。
从 python.org 官网下载安装包时,新手最容易漏掉的就是第一屏底部的Add python.exe to PATH选项。这个选项默认是关闭的,很多人直接点了 Install Now,装完之后在命令行敲python,系统回一句“不是内部或外部命令”。然后就开始怀疑自己是不是电脑有问题。问题不是你操作不对,而是 PATH 没勾上——它决定了系统能不能在任意目录里找到python命令。
装的时候记住两件事:第一,勾选Add python.exe to PATH;第二,选Install Now让它按默认设置装完,不要自己去定制安装路径。装完之后打开命令行验证一下:在 Windows 上输入py --version或者python --version,能看到版本号就说明装好了。如果你用的是 Windows 商店版 Python,在命令行输入python也可能直接能进,但商店版的安装位置和官网版不一样,后期装第三方库时容易遇到权限问题,所以我不建议新手用商店版。
2.2 解释器、编辑器、IDE 到底有什么区别
很多初学者把“装 Python”理解成“装了一个能写代码的软件”,这是一个大误会。装 Python 装的是“解释器”——可以把 .py 文件一行一行翻译成电脑能执行指令的程序。而写代码用的编辑器,是另一个独立的东西。
用生活里的例子来类比:解释器是发动机,负责让代码跑起来;编辑器是工作台,负责让你舒服地写代码。很多人一上来就用记事本写代码,然后双击 .py 文件,一个黑窗闪了一下就没了,于是以为程序出错了。其实程序可能跑得很好,只是 windows 下面双击 .py 默认不会停留。
新人最容易出现的困惑还有这几个词:
- 解释器:从 python.org 装下来的那个东西,负责运行 .py 文件。
- 编辑器:像记事本但更强的工具,负责写代码,比如 VS Code、Sublime。
- IDE:集成开发环境,等于“编辑器 + 调试器 + 项目管理 + 终端”全家桶,比如 PyCharm。
新手不需要把这三者的边界抠得很死,但至少要清楚:你是在“工作台”上写代码,然后让“发动机”去跑它。这样后面遇到“代码明明写对了但运行不了”的时候,基本的排查方向才不会有问题。
2.3 就新手而言,我推荐你从哪个环境开始
Python 的编辑器选择特别多,我在教别人的时候,最怕的就是对方还没写几行代码,先花一下午研究各种编辑器的插件配置。第一集这个阶段,目标是用最少的折腾成本,把代码跑起来。
我按“折腾成本”和“后续上限”给常用环境做个对比:
| 环境 | 适合谁 | 优点 | 缺点 |
|---|---|---|---|
| IDLE | 完全零基础 | 装 Python 自带,零配置,按 F5 就能跑 | 界面简陋,不适合大项目 |
| Thonny | 零基础且想看变量变化 | 自带 Python,调试可视化很好 | 布局有点花哨 |
| VS Code | 想长期写 Python 的人 | 轻量、插件生态全,以后做什么都用得上 | 需要配一次 Python 扩展 |
| PyCharm | 想直接上完整 IDE 的人 | 功能全、提示强 | 偏重,启动慢,新手容易迷路 |
我自己的建议是:如果你第一次接触编程,直接从 IDLE 开始,用它写完这一集的计算器完全没问题。等你跑通了这一集,再考虑转到 VS Code 都不迟。如果你已经能接受“配置环境”本身也是学习的一部分,那直接就上 VS Code,装官方的 Python 扩展,然后新建一个calculator.py文件,按 F5 运行,配好之后也是很顺的。
不推荐一上来就装一堆插件、配一堆主题。环境是服务写代码的,不是用来把玩收藏的。
3. 第一版代码:能算就算,不上任何花架子
3.1 先把流程在脑子里跑一遍
写第一版代码之前,我先带你把流程走一遍。这一步非常关键,因为新手最大的毛病就是打开编辑器就开始敲,敲到哪算哪。设计软件可以不用画图,但脑子里必须有一条线。
我们这一版计算器的流程很简单:
- 提示用户输入第一个数。
- 提示用户输入运算符。
- 提示用户输入第二个数。
- 根据运算符做对应的计算。
- 把结果打印出来。
- 结束程序。
注意,这一版是“一次性”的,跑完就退出。没关系,先用最简单的方式把主流程跑通,后面再给它加循环、加防御。
为什么我强调要先在脑子里跑一遍流程?因为编程本质上就是把你脑子里的流程翻译成代码。如果你连流程都没想清楚,写出来的代码一定是乱的。这个习惯我从第一集开始就让你培养,越到后面越能看出价值。
3.2 第一版代码,每一步都注释
打开你的 IDLE 或编辑器,新建一个文件,存成calculator.py,然后敲入下面的代码。每一行的作用我都写在注释里,但请你记住,光看没用,一定要亲手敲一遍。
# 接收用户输入,input 返回的一定是字符串 num1 = input("请输入第一个数: ") op = input("请输入运算符(+、-、*、/): ") num2 = input("请输入第二个数: ") # 字符串不能参与数学运算,要先转换成 float 类型 num1 = float(num1) num2 = float(num2) # 根据运算符做不同的计算 if op == "+": result = num1 + num2 elif op == "-": result = num1 - num2 elif op == "*": result = num1 * num2 elif op == "/": result = num1 / num2 else: result = "不支持的运算符" # 输出结果 print("结果:", result)运行方式很简单:用 IDLE 打开文件后按 F5,或者在命令行进到文件所在目录,输入python calculator.py。你会在命令行窗口里看到程序开始向你提问。
这里有两个细节我想多说一句。第一个是为什么用float而不是int。如果只用int,你输入 5 和 2 还能算整数除法,但一旦输入 5.5 就会报错;更重要的是,5 / 2的结果是 2.5,需要浮点数类型来接住。用float可以同时接受整数和小数输入,通用性更强。
第二个细节是input()的返回值。它返回的一定是字符串,哪怕你输的是数字,在 Python 看来也是一个字符序列。所以必须用float()包一层,把它转成真正能参与数学运算的数字。这个“类型转换”的概念是 Python 新手第一天就会遇到、以后每天都会遇到的东西,这里先留个印象。
3.3 用这几个用例测一下
代码写完不是结束,测试才是真正的开始。我知道现在说测试对新手来说有点早,但哪怕是三行代码,也应该养成“跑一遍用例看看”的习惯。因为只有跑过真实用例,你才知道代码到底行不行。
我建议你把下面几组输入挨个测一遍:
| 输入 | 预期输出 | 说明 |
|---|---|---|
| 第一个数 3,运算符 +,第二个数 7 | 10.0 | 基础的加法 |
| 第一个数 7,运算符 -,第二个数 10 | -3.0 | 结果可以是负数 |
| 第一个数 6,运算符 *,第二个数 9 | 54.0 | 乘法 |
| 第一个数 8,运算符 /,第二个数 3 | 2.6666666666666665 | 浮点除法,很正常 |
| 第一个数 8,运算符 /,第二个数 0 | 程序报错 | ZeroDivisionError |
| 第一个数 abc,运算符 +,第二个数 3 | 程序报错 | ValueError |
看到没有?正常流程全部能跑通,但一遇到异常输入,程序就崩了。这就是第一版代码的真实状态,也是所有“能跑”的程序最初的状态。没关系,知道它哪里弱,下一步就补哪里。
3.4 第一版代码的三个毛病
第一版代码有三个明显问题,每一个都值得你在写第二版之前想明白。
第一个问题,程序只能用一次。算完一道题就退出,用户想算第二道,得重新运行整个程序。这对计算器来说是不可接受的,因为真实世界的软件几乎都有“循环服务”的特性。
第二个问题,程序太脆。用户只要输入了不是数字的内容,或者做除法时把第二个数输成 0,程序就抛出一大段报错然后终止。任何软件都不可能要求用户“永远输入正确”,所以程序员必须在代码里预先想到错误,并用合适的方式拦截它。
第三个问题,代码重复和结构混乱。float()这种类型转换要做两次,print("结果:", result)这种输出逻辑也只写了一遍,但如果你后面想加新的运算符,就得不停地往if链里塞分支,代码会越来越乱。这其实是“可维护性”的问题,第一版你感受不深,等项目变大你就会懂了。
这三个毛病,正好对应了第二版我们要做的三件事:加循环、加异常处理、抽函数。
4. 第二版:从“能用”变成“扛造”
4.1 用 while True 让程序活起来
我第一次写完第一版的时候,室友过来试了一下,问了一句:“你这计算器怎么算完一次就退出了?真正的计算器哪有算一道题就关机的。”当时我愣了半天,才意识到程序里缺一个“循环”。
Python 里让一段逻辑反复执行的方式有很多,最直接的组合是while True加上break。while True的意思是“条件永远为真,就一直执行下去”,所以它必须配合一个退出条件,否则程序就死循环了。我们的退出条件就是用户输入quit。
while True: # 这里放每次循环要执行的代码 if 用户输入了退出命令: break用大白话说:程序启动后进入一个没有尽头的循环,每循环一次就接待一次用户的计算请求,直到用户说“我要退出”,break才会把循环打断,让程序走到收尾阶段。这个模式你以后会在几乎所有的命令行工具里看到,比如聊天机器人、配置向导、管理终端,本质都是这种“死循环 + 退出条件”的结构。
quit的判断还要注意一点:用户可能输入Quit、QUIT。处理方法是统一转成小写再比较,也就是expr.lower() == "quit"。这种小细节看似不值一提,但会让你的程序显得更“人性化”。
4.2 异常处理:程序不能一犯错就崩
第一版里,用户输入abc或是把 0 当除数,程序直接抛异常闪退。真实软件绝对不能这样。异常处理的思路很简单:在可能出错的代码外面包一层try,出错之后的补救逻辑放在except里。
try: # 这里放可能出错的代码 num1 = float(parts[0]) except ValueError: # 如果真的出错了,就执行这里 print("数字不是有效的数字,请重新输入")float("abc")会抛ValueError,因为字符串"abc"无法转换成数字。捕获到异常后,我们不是把错误扔给用户看,而是友好地提示一句,然后让程序回到循环的起点,重新等待输入。
除数为 0 的情况要单独处理。Python 里8 / 0抛的是ZeroDivisionError,但我更推荐在计算函数里提前判断,而不是等它抛出异常。因为“除数为 0”在计算器场景里本质上是业务规则问题,业务规则应该在结果返回之前就拦住,而不是让异常机制替我们兜底。这也是为什么我在下面的完整代码里写了if num2 == 0: return "除数不能为0"。
另外,except后面至少要写具体的异常类型。只写一个光秃秃的except:确实能兜住所有错误,但它也会把你代码里的 bug 一起吞掉。我的建议是:先捕获你知道一定会发生的异常,比如ValueError,最后再加一个except Exception as e:做兜底打印,这样程序不会毫无提示地崩,同时你也能看到真实错误信息。
4.3 计算逻辑交给函数
把计算逻辑抽成一个独立的函数,是第二版里结构上最重要的改动。为什么要这么做?因为主流程只需要“调用函数、拿结果”,根本不用关心加减乘除到底是怎么算的。函数像一个黑盒,把细节封装在内部,主流程反而变得非常清爽。
def calculate(num1, operator, num2): if operator == "+": return num1 + num2 elif operator == "-": return num1 - num2 elif operator == "*": return num1 * num2 elif operator == "/": if num2 == 0: return "除数不能为0" return num1 / num2 else: return "不支持的运算符"要注意的是,函数里用了return而不是print。return是把结果交还给调用者,调用者可以决定拿这个结果去干什么,比如继续参与运算、格式化输出、写入日志。而print是直接把结果显示到屏幕。第二版里,计算函数的职责是“算出结果”,显示工作交给主流程,这样职责单一,以后想改显示格式,就不用动计算逻辑。
如果你已经在一些教程里见过字典,这里还有一个更精简的写法:用字典映射运算符到对应的计算函数。等到你熟悉 lambda 之后可以试试,但第一集我建议你先把if分支写熟练。
4.4 完整第二版代码
下面是第二版的完整代码。我把输入格式从“分三次输入”改成了“一次输入完整算式”,比如直接输入3 + 5。这样更接近真实计算器的交互习惯。
def calculate(num1, operator, num2): if operator == "+": return num1 + num2 elif operator == "-": return num1 - num2 elif operator == "*": return num1 * num2 elif operator == "/": if num2 == 0: return "除数不能为0" return num1 / num2 else: return "不支持的运算符" print("简单计算器:输入格式为 数字 运算符 数字,比如 3 + 5") print("输入 quit 可以退出") while True: expr = input(">>> ") # 检测退出命令 if expr.lower() == "quit": break # 用空格把算式拆分成三部分 parts = expr.split() # 长度不是 3,说明用户输入的格式不对 if len(parts) != 3: print("格式不对,正确格式是:数字 运算符 数字,中间用空格隔开") continue try: num1 = float(parts[0]) operator = parts[1] num2 = float(parts[2]) result = calculate(num1, operator, num2) # 如果结果是浮点数,保留两位小数,避免显示一长串 if isinstance(result, float): result = round(result, 2) print("结果:", result) except ValueError: print("数字部分不是有效的数字,请重新输入") except Exception as e: print("出现错误:", e) print("程序已退出")这段代码你已经能看到一个“工程化”的影子了:主流程只负责读输入、调用函数、打印结果;具体的计算和错误处理都在各自的位置。测试用例也升级一下:
- 输入
3 + 5,输出8。 - 输入
8 / 3,输出2.67(因为我用round()保留两位小数)。 - 输入
8 / 0,输出除数不能为0,程序不崩溃。 - 输入
abc,提示格式不对,程序继续等待。 - 输入
quit,程序退出。
每一个用例都通过之后,这一集的核心目标就算完成了。但我还想再跟你聊几个很容易踩到的坑。
5. 这些坑我替你踩过了:新手最容易翻车的几个细节
5.1 eval() 这一句话的诱惑
你可能很快会发现,Python 里有个eval()函数,能把一个字符串直接当代码执行。也就是说,你只要eval("3 + 5"),结果就是8。甚至你都不用费劲写float()、if、split()那一堆东西,三行代码就能把计算器写完。
我必须非常严肃地说一句:计算器练习里,eval()是绝对不要用的。原因很简单,eval()会把用户输入的内容当成 Python 代码去执行。你以为用户只会输入3 + 5,但如果有人输入了一段用来删除文件的命令,你的程序会毫无保留地替他把这个命令执行掉。这相当于你把自己的电脑大门钥匙交给了陌生人。
我知道有些人会说:“我自己本地练习,不会有别人来用。”但用具永远会在你最没想到的时候暴露问题。更重要的不是工具本身,而是习惯——不要在你的代码路径里引入“把外部输入当代码执行”的开关。这个原则,等你以后写真正的服务时,会比现在体会到一百倍。
5.2 输入法切换出的全角符号
这个坑我第一次是在帮别人调试时发现的。用户输入3 + 5,中间用的是中文输入法打出来的+和全角空格,导致split()之后运算符变成了+而不是+,程序就报“不支持”。
解决办法有两个层次。第一层是提示用户:请确保运算符是英文半角字符,前后用普通空格隔开。第二层是在代码里做一层清洗,把常见的全角符号替换成半角:
expr = expr.replace("+", "+") expr = expr.replace("-", "-") expr = expr.replace("×", "*") expr = expr.replace("÷", "/")这几行不需要追求优雅,它的价值在于让你的程序对“脏输入”更宽容。真实的用户永远不会按照你想象的方式输入,写防御性代码不是过度设计,而是基本功。我在第二版代码里没有放这段,就是想把“清洗输入”这个意识留给你,你可以自己决定加在哪里。
5.3 /、//、% 的区别
Python 里除法相关的运算符有三个,新手经常搞混:
/是普通除法,结果一定是浮点数,比如8 / 3得到2.6666666666666665。//是整除,只保留整数部分,比如8 // 3得到2。%是取余数,比如8 % 3得到2。
计算器应该用哪个?当然用/。因为计算器要尽量精确,不能用户输入8 / 3你告诉他结果是2,那在数学上是错的。我在第二版里又加了一行round(result, 2),是为了显示的时候避免一串小数,但底层计算并没有损失精度。
顺带一提,很多人一开始会把==和=搞混。=是赋值,==是比较。这个坑你大概率会遇到,看到SyntaxError或者if不生效的时候,先检查是不是写成了单个等号。
5.4 Windows 下双击 py 文件闪退
双击.py文件,一个黑框闪过就消失,这大概是 Windows 初学者最困惑的场景之一。原因通常是文件被关联到了pythonw.exe,它运行程序时不会保留控制台窗口,程序跑完窗口就关了,你根本看不到输出。
正确做法是打开命令行,先用cd进入文件所在目录,再执行python calculator.py。这样程序会在同一个窗口里启动,跑完之后窗口还在,你能看到全部输出。我见过的很多新手因为双击闪退,误以为代码有问题,白白浪费了大量时间排查。所以这里专门说一次:命令行运行,比双击运行更适合调试。
6. 从计算器出发:同一套骨架能长出的五种分支
6.1 支持连续复杂算式
现在的程序只支持数字 运算符 数字这种最简单的三元算式。你想让它支持3 + 5 * 2 - (4 / 2)这种带优先级和括号的表达式,就得引入真正的表达式解析。通常的做法是把字符串先拆成“词法单元”,再用调度场算法或递归下降解析把它转成后缀表达式,最后用栈来计算结果。这些都是后面可以单独开一集讲的内容。
但你现在不用慌。先把第二版跑熟,理解“循环 + 函数 + 异常处理”这套骨架,解析复杂表达式的时候只是在这个骨架上多加几层逻辑,不会推倒重来。
6.2 从命令行到图形界面
同样是计算器,从命令行搬到窗口里,体验完全是两回事。Python 自带的tkinter足够做这件事,加几个按钮、一个显示框,计算逻辑完全复用你现在的calculate()函数。
到时候你会理解一个非常重要的概念:逻辑和界面分离。计算逻辑放在函数里,界面只负责收集用户点击,然后把参数传给函数,再把结果显示出来。界面如何变化,都不影响计算逻辑。这就是“函数封装”带来的最直接的好处。
6.3 打包成 exe,发给朋友用
你辛辛苦苦写的计算器,如果只能在命令行里跑,朋友用起来是有点门槛的。用 PyInstaller 可以把它打包成一个.exe文件,别人双击就能运行。命令很简单:pyinstaller -F calculator.py。但打包过程经常会遇到各种环境问题,比如杀毒软件误报、路径不对、文件太大,这也是一个值得单独研究的领域。
6.4 连续剧下一集预告
第一集的核心不是计算器本身,而是你亲手搭出了一个“输入 → 处理 → 输出 → 循环 → 防御”的程序骨架。以后不管是写游戏、写爬虫、写自动化脚本,这套骨架都会反复出现。
下一集,我会用同样的骨架做一个更好玩的东西——猜数字游戏。你会看到随机数、条件判断、循环和计分逻辑是怎么组合在一起的,也会对“程序如何管理状态”有新的理解。先把这一集的计算器亲手敲一遍,跑通测试,我们再继续往下走。