📌 版本说明:本教程支持 Python 3.8+,部分类型注解特性需要 Python 3.9+,旧版本可使用兼容的类型导入。

🎯 教程说明:本教程分为基础单文件版进阶模块化版,零基础用户可以直接使用单文件版快速运行,有基础的用户可以深入学习模块化设计与测试。

创建的文件

基础版(零基础用户)

仅需 1 个文件,复制即可运行:

  1. calculator.py - 单文件计算器,包含所有功能,无需拆分模块

进阶版(有基础用户)

模块化拆分的项目结构,便于维护与扩展:

  1. calculator - 计算器包

    • __init__.py - 包文件,简化导入,提供公共 API

    • operations.py - 运算模块,包含加减乘除函数

    • input_handler.py - 输入模块,处理用户输入和异常检测

    • output_display.py - 输出模块,格式化展示计算结果

    • controller.py - 控制模块,协调整个计算流程

  2. main.py - 主程序入口

  3. test_calculator.py - 测试脚本,验证所有功能

    • 包含 12 个测试用例,覆盖正常运算、边界条件、异常输入等场景

    • 所有测试通过 ✅

运行方式

基础版运行(零基础用户)

你只需要运行单文件即可:

1
python calculator.py

进阶版运行(有基础用户)

1
2
3
4
5
6
# 进入项目目录
cd calculator_project
# 运行主程序
python main.py
# 运行测试(需要先安装pytest:pip install pytest)
python -m pytest test_calculator.py -v

Python 简易计算器教程


计算器项目是编程入门的经典实践案例,它不仅能帮助初学者掌握基础语法,还能深入理解模块化设计、函数封装、异常处理等概念。本文将从基础到进阶,指导你构建一个功能完整的计算器,同时兼顾不同基础的学习者。

1. 零基础入门:单文件计算器

零基础用户:你可以直接复制下面的代码到calculator.py文件中,直接运行即可,我们会逐步解释每一部分的作用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
def add(a: float, b: float) -> float:
"""加法运算"""
return a + b

def subtract(a: float, b: float) -> float:
"""减法运算"""
return a - b

def multiply(a: float, b: float) -> float:
"""乘法运算"""
return a * b

def divide(a: float, b: float) -> float:
"""除法运算,处理除零错误"""
if b == 0:
raise ValueError("除数不能为零")
return a / b

def calculate(a: float, b: float, operator: str) -> float:
"""根据运算符执行对应运算"""
# 运算符到函数的映射表
op_functions = {
'+': add,
'-': subtract,
'*': multiply,
'/': divide
}
# 验证运算符合法性
if operator not in op_functions:
raise ValueError(f"不支持的运算符: {operator}")
# 执行运算并返回结果
return op_functions[operator](a, b)

def get_user_input():
"""获取用户输入的两个数字和一个运算符"""
while True:
try:
# 获取第一个操作数
num1_str = input("\n请输入第一个数字 (输入q退出): ")
if num1_str.lower() == 'q':
print("程序已退出")
exit(0)
num1 = float(num1_str)

# 获取运算符
operator = input("请输入运算符 (+, -, *, /) 或输入q退出: ").strip()
if operator.lower() == 'q':
print("程序已退出")
exit(0)
if operator not in ['+', '-', '*', '/']:
raise ValueError(f"不支持的运算符: {operator}")

# 获取第二个操作数
num2_str = input("请输入第二个数字 (输入q退出): ")
if num2_str.lower() == 'q':
print("程序已退出")
exit(0)
num2 = float(num2_str)

# 返回验证后的数据
return num1, num2, operator
except ValueError as e:
print(f"\n输入错误: {e},请确保:")
print("- 数字可以是整数或小数(如 5 或 3.14)")
print("- 运算符只能是 +, -, *, / 中的一个")
print("- 输入q可随时退出程序")
continue

def show_result(a: float, b: float, operator: str, result: float) -> None:
"""展示计算结果,使用友好的格式"""
print(f"\n计算结果: {a} {operator} {b} = {result:.4f}")
print("-" * 30) # 分隔线,便于多轮计算

def run_calculator() -> None:
"""计算器主程序,控制整个计算流程"""
print("\n简易计算器程序")
print("支持 +, -, *, / 四种运算")
print("输入q可随时退出程序")
print("-" * 30)
while True:
try:
# 获取用户输入
a, b, op = get_user_input()
# 执行计算
result = calculate(a, b, op)
# 显示结果
show_result(a, b, op, result)
except ValueError as e:
print(f"\n错误: {e}")
print("请检查输入并重新尝试。")
print("-" * 30)
continue

if __name__ == "__main__":
run_calculator()

零基础小提示:这个单文件版本把所有功能都放在一个文件里,你不需要理解复杂的模块,直接运行就可以使用计算器了,等你掌握了基础,我们再学习进阶的模块化设计。

2. 进阶学习:模块化计算器📚

有基础的用户:这部分我们会把代码拆分成独立的模块,学习 Python 的包和模块化设计,让代码更易维护和扩展。

2.1 需求分析与模块划分

模块化设计意味着将软件系统划分为若干个功能相对独立的模块,每个模块负责不同的功能,这些模块之间通过定义明确的接口进行通信。对于一个四则运算计算器,我们可以划分为以下四个主要模块:

  1. 输入模块:负责接收用户输入的数值和运算符,验证输入有效性

  2. 运算模块:执行具体的加减乘除运算,处理运算异常

  3. 输出模块:将计算结果格式化展示给用户

  4. 控制模块:管理各个模块之间的交互和数据传递

这种模块化设计带来了以下优势:

  • 代码清晰,易于理解:每个模块专注于单一功能

  • 便于维护和调试:修改或修复一个模块不会影响其他模块

  • 提高代码复用性:功能模块可被其他项目重用

  • 支持团队协作:不同开发者可同时在不同模块上工作

2.2 项目目录结构

我们将项目组织为以下结构:

Text
1
2
3
4
5
6
7
8
9
calculator_project/
├── calculator/ # 计算器包
│ ├── __init__.py # 包文件,提供公共API
│ ├── operations.py # 运算模块
│ ├── input_handler.py # 输入模块
│ ├── output_display.py # 输出模块
│ └── controller.py # 控制模块
├── main.py # 主程序入口
└── test_calculator.py # 测试脚本

2.3 各模块代码实现

2.3.1 运算模块:operations.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from typing import Dict, Callable

def add(a: float, b: float) -> float:
"""加法运算"""
return a + b

def subtract(a: float, b: float) -> float:
"""减法运算"""
return a - b

def multiply(a: float, b: float) -> float:
"""乘法运算"""
return a * b

def divide(a: float, b: float) -> float:
"""除法运算,处理除零错误"""
if b == 0:
raise ValueError("除数不能为零")
return a / b

def calculate(a: float, b: float, operator: str) -> float:
"""根据运算符执行对应运算"""
# 运算符到函数的映射表,使用正确的函数类型注解
op_functions: Dict[str, Callable[[float, float], float]] = {
'+': add,
'-': subtract,
'*': multiply,
'/': divide
}
# 验证运算符合法性
if operator not in op_functions:
raise ValueError(f"不支持的运算符: {operator}")
# 执行运算并返回结果
return op_functions[operator](a, b)

2.3.2 输入模块:input_handler.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

from typing import Tuple

def get_user_input() -> Tuple[float, float, str]:
"""获取用户输入的两个数字和一个运算符"""
while True:
try:
# 获取第一个操作数
num1_str = input("\n请输入第一个数字 (输入q退出): ")
if num1_str.lower() == 'q':
print("程序已退出")
exit(0)
num1 = float(num1_str)

# 获取运算符
operator = input("请输入运算符 (+, -, *, /) 或输入q退出: ").strip()
if operator.lower() == 'q':
print("程序已退出")
exit(0)
if operator not in ['+', '-', '*', '/']:
raise ValueError(f"不支持的运算符: {operator}")

# 获取第二个操作数
num2_str = input("请输入第二个数字 (输入q退出): ")
if num2_str.lower() == 'q':
print("程序已退出")
exit(0)
num2 = float(num2_str)

# 返回验证后的数据
return num1, num2, operator
except ValueError as e:
print(f"\n输入错误: {e},请确保:")
print("- 数字可以是整数或小数(如 5 或 3.14)")
print("- 运算符只能是 +, -, *, / 中的一个")
print("- 输入q可随时退出程序")
continue

2.3.3 输出模块:output_display.py

1
2
3
4
5

def show_result(a: float, b: float, operator: str, result: float) -> None:
"""展示计算结果,使用友好的格式"""
print(f"\n计算结果: {a} {operator} {b} = {result:.4f}")
print("-" * 30) # 分隔线,便于多轮计算

2.3.4 控制模块:controller.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

from calculator import get_user_input, calculate, show_result

def run_calculator() -> None:
"""计算器主程序,控制整个计算流程"""
print("\n简易计算器程序")
print("支持 +, -, *, / 四种运算")
print("输入q可随时退出程序")
print("-" * 30)
while True:
try:
# 获取用户输入
a, b, op = get_user_input()
# 执行计算
result = calculate(a, b, op)
# 显示结果
show_result(a, b, op, result)
except ValueError as e:
print(f"\n错误: {e}")
print("请检查输入并重新尝试。")
print("-" * 30)
continue

2.3.5 包初始化文件:init.py

这个文件用来标记这是一个 Python 包,同时简化导入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 从子模块导入关键函数,简化主程序调用
from .input_handler import get_user_input
from .operations import calculate
from .output_display import show_result
from .controller import run_calculator

# 定义公共API,控制`from calculator import *`的导入行为
__all__ = [
"get_user_input",
"calculate",
"show_result",
"run_calculator"
]

2.3.6 主程序入口:main.py

1
2
3
4
5

from calculator import run_calculator

if __name__ == "__main__":
run_calculator()

2.4 进阶:单元测试

有基础用户:我们可以使用 pytest 来测试我们的代码,确保所有功能都正常工作。

创建test_calculator.py文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96

import pytest
from io import StringIO
from unittest.mock import patch
from calculator import get_user_input, calculate, run_calculator
from calculator.operations import add, subtract, multiply, divide

# 参数化测试四则运算
@pytest.mark.parametrize(
"a, b, op, expected",
[
(5, 3, '+', 8),
(10, 2, '-', 8),
(4, 5, '*', 20),
(20, 5, '/', 4.0),
(0, 0, '+', 0), # 零值测试
(10.5, 2.5, '*', 26.25), # 小数测试
(-5, 3, '+', -2),
(5, -3, '-', 8),
(-4, -5, '*', 20),
(-10, -2, '/', 5.0),
]
)
def test_valid_operations(a, b, op, expected):
"""测试合法运算符的计算结果"""
result = calculate(a, b, op)
assert result == pytest.approx(expected, rel=1e-6)

def test_zero_division():
"""测试除零错误是否正确触发"""
with pytest.raises(ValueError):
divide(10, 0)

def test_invalid_operator():
"""测试非法运算符是否正确抛出异常"""
with pytest.raises(ValueError):
calculate(5, 3, '%')

def test_input_handler_valid():
"""测试有效输入流程"""
inputs = iter(["5", "+", "3"])
with patch('builtins.input', side_effect=inputs):
a, b, op = get_user_input()
assert a == 5.0 and b == 3.0 and op == '+'

def test_input_handler_non_numeric():
"""测试非数字输入处理"""
inputs = iter(["abc", "10", "/", "2"])
with patch('builtins.input', side_effect=inputs):
a, b, op = get_user_input()
assert a == 10.0 and b == 2.0 and op == '/'

def test_input_handler_empty():
"""测试空输入处理"""
inputs = iter(["", " ", "5", "*", "3"])
with patch('builtins.input', side_effect=inputs):
a, b, op = get_user_input()
assert a == 5.0 and b == 3.0 and op == '*'

def test_input_handler_exit():
"""测试退出命令处理"""
inputs = iter(["q"])
with patch('builtins.input', side_effect=inputs):
with pytest.raises(SystemExit):
get_user_input()

def test_full_cycle():
"""测试完整计算流程"""
inputs = iter(["5", "+", "3"])
with patch('builtins.input', side_effect=inputs):
with patch('sys.stdout', new=StringIO()) as output:
run_calculator()
assert "计算结果: 5.0 + 3.0 = 8.0000" in output.getvalue()

def test_zero_division_flow():
"""测试除零错误处理流程"""
inputs = iter(["10", "/", "0"])
with patch('builtins.input', side_effect=inputs):
with patch('sys.stdout', new=StringIO()) as output:
run_calculator()
assert "除数不能为零" in output.getvalue()

def test_invalid_operator_flow():
"""测试非法运算符处理流程"""
inputs = iter(["5", "%", "3"])
with patch('builtins.input', side_effect=inputs):
with patch('sys.stdout', new=StringIO()) as output:
run_calculator()
assert "不支持的运算符: %" in output.getvalue()

def test_exit_command_flow():
"""测试退出命令处理流程"""
inputs = iter(["q"])
with patch('builtins.input', side_effect=inputs):
with pytest.raises(SystemExit):
run_calculator()

3. 模块化设计的优势与扩展性📚

3.1 模块化带来的优势

  1. 代码清晰,易于理解:每个模块专注于单一功能,代码结构清晰

  2. 便于维护和调试:修改或修复一个模块不会影响其他模块的功能

  3. 提高代码复用性:运算模块可被其他需要四则运算的程序重用

  4. 支持团队协作:不同开发者可同时在不同模块上工作,互不影响

  5. 简化测试流程:单元测试可独立测试每个模块,集成测试验证模块交互

3.2 未来扩展性

当前的模块化设计为计算器的未来扩展提供了良好的基础:

  1. 添加新运算:只需在operations.py中添加新函数,并更新op_functions字典

  2. 添加历史记录:可以新增一个history.py模块,记录用户的计算历史

  3. 支持科学计算:可以扩展运算模块,添加开方、幂运算等功能

  4. 支持 GUI 界面:可以替换输入输出模块,使用 GUI 界面而不是命令行