当前位置: 首页 > news >正文

Python快速入门专业版(三十四):函数实战1:计算器程序(支持加减乘除与括号优先级)

在这里插入图片描述

目录

  • 一、需求分析:计算器的核心功能与技术要点
    • 1. 核心功能
    • 2. 技术难点
  • 二、模块拆分:将计算器功能拆解为小函数
  • 三、代码实现:分模块编写核心函数
    • 1. 基础运算函数:实现加减乘除
    • 2. 表达式解析函数:拆分数字与运算符
    • 3. 无括号表达式计算:处理乘除与加减优先级
    • 4. 带括号表达式计算:递归消除括号
    • 5. 主函数:整合功能与异常处理
  • 四、代码测试与功能验证
    • 1. 基础运算测试
    • 2. 括号优先级测试
    • 3. 负数与复杂表达式测试
    • 4. 异常场景测试
  • 五、代码优化与扩展思路
    • 1. 代码优化
    • 2. 功能扩展
  • 六、实战总结:函数模块化编程的核心思想

在Python函数学习中,单一功能的函数练习往往难以体现实战价值。而计算器程序作为经典的综合案例,能将函数定义、表达式解析、优先级处理、异常捕获等知识点串联起来,完美展现“模块化编程”的思想——通过拆分复杂问题(如表达式计算)为多个小函数(基础运算、括号处理、优先级排序),最终组合实现完整功能。

本文将从零开始构建一个支持“加减乘除”和“括号优先级”的简易计算器,详细讲解从需求分析到代码实现的全过程,包括基础运算函数定义、表达式解析逻辑、运算符优先级处理(先乘除后加减,括号优先),以及异常处理(除数为0、格式错误等),帮助你掌握函数在实战中的综合应用。

一、需求分析:计算器的核心功能与技术要点

在编写代码前,需明确计算器的功能边界和技术难点,确保开发目标清晰。

1. 核心功能

  • 支持四种基础运算:加法(+)、减法(-)、乘法(*)、除法(/)。
  • 遵循数学运算优先级:括号内的表达式优先计算,其次是乘除运算,最后是加减运算
  • 支持多位数、小数(如“12.3+4.5*2”)和负数(如“-5+3”“(2-5)*4”)。
  • 输入输出友好:接收用户输入的表达式字符串(如“(1+2)*3-4/2”),输出计算结果,并处理异常情况(如除数为0、表达式格式错误)。

2. 技术难点

  • 表达式解析:将字符串形式的表达式(如“1+2*3”)拆分为“数字”和“运算符”两部分(如数字列表[1,2,3],运算符列表['+', '*'])。
  • 括号处理:识别表达式中的最内层括号,递归计算括号内结果,并用结果替换括号部分,逐步消除括号。
  • 优先级排序:在无括号的表达式中,先执行乘除运算,再执行加减运算,需动态调整数字和运算符列表。
  • 异常处理:覆盖常见错误场景(除数为0、括号不匹配、非数字输入、运算符位置错误等)。

二、模块拆分:将计算器功能拆解为小函数

遵循“单一职责原则”,将计算器拆分为以下核心函数,每个函数专注于解决一个具体问题:

函数名称功能描述输入输出
add(a, b)实现两个数的加法两个数字(int/float)加法结果(int/float)
subtract(a, b)实现两个数的减法两个数字(int/float)减法结果(int/float)
multiply(a, b)实现两个数的乘法两个数字(int/float)乘法结果(int/float)
divide(a, b)实现两个数的除法(处理除数为0)两个数字(int/float)除法结果(int/float)
parse_expression(expr)解析表达式字符串,拆分数字和运算符表达式字符串(如“1+2*3”)元组(numbers, operators),分别为数字列表和运算符列表
calculate_no_brackets(numbers, operators)计算无括号的表达式(处理乘除、加减优先级)数字列表、运算符列表计算结果(int/float)
calculate_with_brackets(expr)处理带括号的表达式(递归计算括号内结果)表达式字符串最终计算结果(int/float)
calculator()主函数:接收用户输入,调用其他函数,处理异常打印计算结果或错误提示

三、代码实现:分模块编写核心函数

1. 基础运算函数:实现加减乘除

首先编写四个基础运算函数,其中除法函数需处理“除数为0”的异常,为后续计算提供稳定的基础功能。

def add(a: float, b: float) -> float:"""实现两个数的加法运算"""return a + bdef subtract(a: float, b: float) -> float:"""实现两个数的减法运算(a - b)"""return a - bdef multiply(a: float, b: float) -> float:"""实现两个数的乘法运算"""return a * bdef divide(a: float, b: float) -> float:"""实现两个数的除法运算(a / b),处理除数为0的异常"""if b == 0:# 抛出自定义异常,便于上层函数捕获raise ZeroDivisionError("除数不能为0")return a / b

解析

  • 每个函数接收两个float类型参数(兼容整数和小数),返回运算结果。
  • divide函数中,若除数b为0,主动抛出ZeroDivisionError异常,由上层函数统一处理,避免程序崩溃。

2. 表达式解析函数:拆分数字与运算符

将用户输入的表达式字符串(如“1+23”)拆分为“数字列表”和“运算符列表”,是后续计算的前提。这里使用正则表达式匹配数字(包括整数、小数、负数)和运算符(+、-、、/、(、)),高效且准确。

import redef parse_expression(expr: str) -> tuple[list[float], list[str]]:"""解析表达式字符串,提取数字和运算符(不含括号)参数:expr - 去除空格后的表达式字符串(如“1+2*3”)返回:(numbers, operators) - 数字列表和运算符列表异常:若表达式格式错误(如连续运算符、缺少数字),抛出ValueError"""# 正则表达式匹配规则:# -?\d+\.?\d*: 匹配负数(-开头)、整数(如123)、小数(如12.34)# [+\-*/]: 匹配加减乘除运算符(注意-需转义,避免与范围符号混淆)pattern = r'-?\d+\.?\d*|[+\-*/]'# 提取所有匹配项tokens = re.findall(pattern, expr)# 分离数字和运算符numbers = []operators = []for token in tokens:if token in '+-*/':operators.append(token)else:try:# 将字符串转换为数字(支持整数和小数)numbers.append(float(token))except ValueError:raise ValueError(f"无效的数字:{token}")# 验证表达式合法性(数字数量 = 运算符数量 + 1)if len(numbers) != len(operators) + 1:raise ValueError("表达式格式错误(可能存在连续运算符或缺少数字)")return numbers, operators

解析

  • 正则表达式r'-?\d+\.?\d*|[+\-*/]' 是核心,分为两部分:
    • (?\d+\.?\d*):匹配数字,-?表示可选负号,\d+表示1个以上数字,\.?\d*表示可选小数点和后续数字(兼容整数和小数)。
    • [+\-*/]:匹配四个运算符(注意-需转义为\-,避免被当作正则中的“范围符号”)。
  • 合法性校验:正常表达式中,数字数量必然比运算符多1(如“a+b*c”有3个数字、2个运算符),若不满足则抛出格式错误。
  • 异常传递:若提取到无法转换为数字的token(如字母),抛出ValueError,由上层函数处理。

3. 无括号表达式计算:处理乘除与加减优先级

对于无括号的表达式(如“1+2*3-4/2”),需遵循“先乘除后加减”的优先级。实现思路是:

  1. 先遍历运算符列表,处理所有“*”和“/”,计算对应结果后更新数字和运算符列表。
  2. 再遍历剩余的运算符列表(仅含“+”和“-”),计算最终结果。
def calculate_no_brackets(numbers: list[float], operators: list[str]) -> float:"""计算无括号的表达式,遵循“先乘除后加减”的优先级参数:numbers - 数字列表(如[1,2,3,4])operators - 运算符列表(如['+', '*', '-', '/'])返回:表达式的计算结果异常:若运算符不合法或除数为0,抛出对应异常"""# 复制列表,避免修改原列表(函数应保持“纯函数”特性,不影响外部数据)nums = numbers.copy()ops = operators.copy()# 第一步:处理乘除运算(优先级高)i = 0while i < len(ops):op = ops[i]if op in ('*', '/'):# 获取当前运算符左右两侧的数字a = nums[i]b = nums[i + 1]# 根据运算符调用对应函数if op == '*':result = multiply(a, b)else:  # op == '/'result = divide(a, b)# 更新数字列表:用计算结果替换a和b(如[1,2,3] → [1,6])nums[i] = resultdel nums[i + 1]# 更新运算符列表:移除已处理的运算符del ops[i]# 无需i+1,因为列表长度已缩短else:# 若为加减运算符,暂不处理,继续遍历i += 1# 第二步:处理加减运算(优先级低,此时运算符列表仅含'+'和'-')result = nums[0]  # 初始值为第一个数字for i in range(len(ops)):op = ops[i]b = nums[i + 1]if op == '+':result = add(result, b)else:  # op == '-'result = subtract(result, b)return result

解析

  • 列表复制:通过nums = numbers.copy()ops = operators.copy()避免修改外部传入的列表,确保函数是“纯函数”(输入不变则输出不变,无副作用)。
  • 乘除处理:遍历运算符列表,遇到“*”或“/”时,计算对应两个数字的结果,用结果替换这两个数字(如[1,2,3][1,6]),并删除已处理的运算符,实现列表“收缩”。
  • 加减处理:乘除处理后,运算符列表仅含“+”和“-”,从左到右依次计算即可。

4. 带括号表达式计算:递归消除括号

括号的优先级最高,需先计算最内层括号内的表达式。实现思路是:

  1. 用正则表达式找到最内层括号(即不包含其他括号的(...))。
  2. 提取括号内的表达式,调用parse_expressioncalculate_no_brackets计算结果。
  3. 用计算结果替换原表达式中的括号部分(如“(1+2)3”→“33”)。
  4. 重复步骤1-3,直到表达式中无括号,最后计算无括号表达式的结果。
def calculate_with_brackets(expr: str) -> float:"""计算带括号的表达式,通过递归消除括号后计算参数:expr - 去除空格后的表达式字符串(如“(1+2)*3-4/2”)返回:表达式的最终计算结果异常:若括号不匹配或表达式格式错误,抛出ValueError"""# 正则表达式匹配最内层括号:\([^()]*\)# \(: 匹配左括号;[^()]*: 匹配不含括号的任意字符(最内层括号特征);\): 匹配右括号bracket_pattern = r'\([^()]*\)'# 循环消除括号,直到表达式中无括号while '(' in expr or ')' in expr:# 查找最内层括号match = re.search(bracket_pattern, expr)if not match:# 若存在括号但未找到匹配(如括号不闭合),抛出异常raise ValueError("括号不匹配(可能存在未闭合或嵌套错误)")# 提取括号内的表达式(如“(1+2)”→“1+2”)bracket_content = match.group()[1:-1]  # [1:-1] 去除左右括号# 解析括号内的表达式并计算结果try:nums, ops = parse_expression(bracket_content)bracket_result = calculate_no_brackets(nums, ops)except ValueError as e:raise ValueError(f"括号内表达式错误:{e}")# 用计算结果替换原括号部分(如“(1+2)”→“3”)expr = expr.replace(match.group(), str(bracket_result))# 表达式已无括号,直接计算结果nums, ops = parse_expression(expr)return calculate_no_brackets(nums, ops)

解析

  • 最内层括号匹配:正则表达式r'\([^()]*\)'是关键,[^()]表示“除括号外的任意字符”,确保只匹配不包含其他括号的最内层括号(如“(a+(bc))”中先匹配“(bc)”)。
  • 递归思想:通过循环而非显式递归,逐步消除括号,每次处理最内层,最终将带括号表达式转化为无括号表达式,降低计算复杂度。
  • 括号校验:若表达式中存在括号但未找到匹配(如“(1+2”缺少右括号),抛出“括号不匹配”异常。

5. 主函数:整合功能与异常处理

主函数calculator()负责串联所有模块:接收用户输入、预处理(去除空格)、调用计算函数、捕获异常并输出结果,是计算器与用户交互的入口。

def calculator():"""计算器主函数:接收用户输入,处理表达式计算,输出结果或错误提示"""print("=" * 50)print("        简易计算器(支持加减乘除与括号优先级)")print("说明:输入表达式(如“(1+2)*3-4/2”),输入“q”退出")print("=" * 50)while True:# 接收用户输入user_input = input("\n请输入表达式:").strip()# 退出逻辑if user_input.lower() == 'q':print("感谢使用,再见!")break# 预处理:去除表达式中的所有空格(如“1 + 2 * 3”→“1+2*3”)expr = user_input.replace(" ", "")# 空输入处理if not expr:print("错误:请输入有效的表达式")continuetry:# 核心计算逻辑result = calculate_with_brackets(expr)# 输出结果(若为整数,去除小数点后多余的0,如6.0→6)if result.is_integer():print(f"计算结果:{int(result)}")else:print(f"计算结果:{result:.2f}")  # 小数保留2位except ZeroDivisionError as e:print(f"计算错误:{e}")except ValueError as e:print(f"格式错误:{e}")except Exception as e:# 捕获其他未预料的异常,避免程序崩溃print(f"未知错误:{str(e)}")# 运行计算器
if __name__ == "__main__":calculator()

解析

  • 用户交互:通过循环持续接收输入,输入“q”时退出,符合计算器的使用习惯。
  • 输入预处理:用replace(" ", "")去除所有空格,支持用户输入带空格的表达式(如“1 + (2 * 3)”)。
  • 异常捕获
    • ZeroDivisionError:处理除数为0的错误。
    • ValueError:处理表达式格式错误(如括号不匹配、连续运算符)。
    • 通用Exception:捕获其他未预料的错误(如内存不足),确保程序稳定运行。
  • 结果格式化:若结果为整数(如6.0),转换为int类型输出(避免“6.0”的冗余显示);若为小数,保留2位小数(如2.50→2.50,1.3333→1.33)。

四、代码测试与功能验证

运行计算器程序后,通过以下测试案例验证核心功能:

1. 基础运算测试

  • 输入:1+2*3 → 预期结果:7(先算2*3=6,再算1+6=7)
  • 输入:10-4/2 → 预期结果:8(先算4/2=2,再算10-2=8)
  • 输入:3.5*2+1.5 → 预期结果:8.5(3.5*2=7,7+1.5=8.5)

2. 括号优先级测试

  • 输入:(1+2)*3 → 预期结果:9(先算括号内1+2=3,再算3*3=9)
  • 输入:10/(2+3) → 预期结果:2(先算括号内2+3=5,再算10/5=2)
  • 输入:(3+2)*(4-1) → 预期结果:15(先算3+2=5、4-1=3,再算5*3=15)

3. 负数与复杂表达式测试

  • 输入:-5+3*2 → 预期结果:1(先算3*2=6,再算-5+6=1)
  • 输入:(2-5)*4+8/2 → 预期结果:-8(先算2-5=-3,-3*4=-12;再算8/2=4;最后-12+4=-8)

4. 异常场景测试

  • 输入:10/0 → 预期提示:计算错误:除数不能为0
  • 输入:1++2 → 预期提示:格式错误:表达式格式错误(可能存在连续运算符或缺少数字)
  • 输入:(1+2*3 → 预期提示:格式错误:括号不匹配(可能存在未闭合或嵌套错误)
  • 输入:a+1 → 预期提示:格式错误:无效的数字:a

五、代码优化与扩展思路

当前计算器已实现核心功能,可从以下方面优化和扩展:

1. 代码优化

  • 支持更多运算符:如取模(%)、幂运算(**),只需在parse_expression的正则表达式中添加运算符,在calculate_no_brackets中增加对应处理逻辑。
  • 优化正则表达式:当前正则对负数的匹配可能存在边缘情况(如“1±2”),可优化为r'(?<![+\-*/(])-?\d+\.?\d*|[+\-*/()]',避免将“1±2”中的“-”误判为负数符号。
  • 缓存计算结果:对于重复输入的表达式(如用户多次输入“(1+2)*3”),可缓存计算结果,减少重复解析和计算时间。

2. 功能扩展

  • 支持历史记录:用列表存储用户输入的表达式和结果,添加“查看历史”功能(输入“history”显示历史记录)。
  • 支持变量赋值:允许用户定义变量(如“a=10”),后续表达式可使用变量(如“a+2*3”)。
  • 图形界面(GUI):使用tkinterPyQt库开发图形界面,替代命令行输入,提升用户体验。

六、实战总结:函数模块化编程的核心思想

通过计算器程序的开发,我们可以提炼出函数实战的核心原则:

  1. 拆分复杂问题:将“表达式计算”这个复杂问题拆分为“基础运算”“表达式解析”“括号处理”“优先级排序”等小问题,每个问题用一个函数解决,降低思维复杂度。
  2. 保持函数单一职责:每个函数只做一件事(如parse_expression仅负责拆分数字和运算符,不参与计算),让函数易于理解、测试和维护。
  3. 函数间低耦合:函数通过参数和返回值传递数据,避免直接修改外部变量(如calculate_no_brackets复制列表而非修改原列表),提高代码的可复用性。
  4. 全面异常处理:预判可能的错误场景(如除数为0、格式错误),用异常捕获机制确保程序稳定,同时给用户友好的提示。

这种“模块化编程”思想不仅适用于计算器开发,更适用于所有复杂程序(如管理系统、数据分析工具)。掌握它,能让你写出更清晰、更健壮、更易扩展的代码。

http://www.dtcms.com/a/389389.html

相关文章:

  • datawhale玩转通义四大新模型 202509 第2次作业
  • 【项目实战】知识库——基础理论1
  • ego(7)---为Astar路径点计算交点,避障基准点与排斥方向
  • Oracle体系结构-警报日志文件 (Alert Log File / Alert SID.log)
  • 解锁 TiDB:供应链场景下分布式分库分表的案例
  • 【IEEE出版 | 早鸟优惠开启】人工智能驱动图像处理与计算机视觉技术国际学术研讨会 (AIPCVT 2025)
  • Ubuntu之旅-02 Redis
  • 基于RK3588+FPGA的无人机飞控系统,支持AI算力和FPGA实时性,强大的图像处理能力,支持全国产化
  • 杂七杂八之基于ApiPost的Jar包调用
  • 华为HCCL集合通信库AllGather算子全流程解析
  • uv管理的python项目怎么打包成docker部署发布上线
  • unity之uv编辑
  • 华为电源研发的IPD实践:从概念到生命周期的结构化管控
  • MySQL中什么是回表查询,如何避免和优化?
  • MySql01
  • 2025版基于springboot的旅游门票预定系统
  • 3. Linux 计划任务管理
  • EasyCVR在智慧城市中场景中的核心应用与实践方案
  • LeetCode 刷题【84. 柱状图中最大的矩形】
  • CPP网络编程基础知识
  • 临床AI产品化全流程研究:环境聆听、在环校验与可追溯系统的多技术融合实践(上)
  • 【k8s】web服务优雅关闭用户连接
  • 设计模式的七大原则总述
  • C/C++柔性数组
  • 从 LiveData 到 Flow:Android 状态管理的现代化演进
  • 34、模型微调技术实战 - LoRA参数高效微调全流程
  • ASP.NET Core 中基于角色的授权
  • C++ 在 Windows 下实现最基础的 WebSocket 服务端与客户端
  • 并发、分布式和实时设计方法
  • C语言第15讲