跳转至

实战项目1:计算器

项目概述

实现一个功能完整的计算器,支持基本运算、科学计算和函数绘图功能。这是一个很好的入门项目,可以帮助你学习GUI编程、事件处理和数学函数的使用。

学习目标

  • 掌握Python GUI编程(tkinter)
  • 学习事件驱动编程
  • 理解表达式求值的原理
  • 掌握异常处理
  • 学习图形绘制(matplotlib)

功能需求

基本功能

  1. 基本运算:加、减、乘、除
  2. 括号优先级:支持括号改变运算顺序
  3. 清除功能:清除当前输入或全部清除
  4. 小数运算:支持小数点运算

进阶功能

  1. 科学计算:三角函数、对数、指数、根号
  2. 记忆功能:存储和读取计算结果
  3. 历史记录:显示计算历史
  4. 错误处理:处理各种错误情况

高级功能

  1. 函数绘图:绘制数学函数图像
  2. 进制转换:二进制、八进制、十六进制转换
  3. 复数运算:支持复数计算

实现方案

方案一:使用tkinter(推荐)

import tkinter as tk
from tkinter import messagebox
import math

class Calculator:
    """计算器类"""

    def __init__(self, root):
        self.root = root
        self.root.title("计算器")
        self.root.geometry("400x600")
        self.root.resizable(False, False)

        self.expression = ""
        self.memory = 0
        self.history = []

        self.create_widgets()

    def create_widgets(self):
        """创建界面组件"""
        # 显示区域
        self.display_var = tk.StringVar()
        self.display = tk.Entry(
            self.root, 
            textvariable=self.display_var,
            font=('Arial', 24),
            justify='right',
            state='readonly'
        )
        self.display.pack(fill=tk.X, padx=10, pady=10)

        # 按钮布局
        buttons = [
            ('C', '±', '%', '÷'),
            ('7', '8', '9', '×'),
            ('4', '5', '6', '-'),
            ('1', '2', '3', '+'),
            ('0', '.', '=', 'M')
        ]

        button_frame = tk.Frame(self.root)
        button_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)

        for i, row in enumerate(buttons):
            for j, text in enumerate(row):
                btn = tk.Button(
                    button_frame,
                    text=text,
                    font=('Arial', 18),
                    command=lambda t=text: self.button_click(t)
                )
                btn.grid(row=i, column=j, sticky="nsew", padx=2, pady=2)
                button_frame.grid_columnconfigure(j, weight=1)
                button_frame.grid_rowconfigure(i, weight=1)

    def button_click(self, text):
        """按钮点击事件处理"""
        if text == 'C':
            self.expression = ""
        elif text == '=':
            try:
                result = self.evaluate(self.expression)
                self.history.append(f"{self.expression} = {result}")
                self.expression = str(result)
            except Exception as e:
                messagebox.showerror("错误", str(e))
                self.expression = ""
        elif text == 'M':
            self.memory = float(self.expression) if self.expression else 0
            messagebox.showinfo("记忆", f"已存储: {self.memory}")
        elif text == '±':
            if self.expression:
                if self.expression[0] == '-':
                    self.expression = self.expression[1:]
                else:
                    self.expression = '-' + self.expression
        else:
            self.expression += text

        self.display_var.set(self.expression)

    def evaluate(self, expression):
        """计算表达式"""
        # 替换运算符
        expression = expression.replace('×', '*').replace('÷', '/')

        # 安全评估
        allowed_chars = set('0123456789+-*/.()')
        if not all(c in allowed_chars or c.isspace() for c in expression):
            raise ValueError("包含非法字符")

        try:
            result = eval(expression)
            return result
        except ZeroDivisionError:
            raise ValueError("除数不能为零")
        except SyntaxError:
            raise ValueError("表达式语法错误")
        except:
            raise ValueError("计算错误")

# 创建主窗口
root = tk.Tk()
calculator = Calculator(root)
root.mainloop()

方案二:命令行计算器

import re

class CommandLineCalculator:
    """命令行计算器"""

    def __init__(self):
        self.memory = 0
        self.history = []

    def evaluate(self, expression):
        """计算表达式"""
        # 替换运算符
        expression = expression.replace('^', '**')

        # 安全检查
        allowed = re.compile(r'^[\d+\-*/(). ]+$')
        if not allowed.match(expression):
            raise ValueError("包含非法字符")

        try:
            result = eval(expression)
            self.history.append(f"{expression} = {result}")
            return result
        except ZeroDivisionError:
            raise ValueError("除数不能为零")
        except:
            raise ValueError("表达式错误")

    def run(self):
        """运行计算器"""
        print("=== 命令行计算器 ===")
        print("输入表达式进行计算,输入 'exit' 退出")
        print("输入 'history' 查看历史记录")
        print("输入 'memory' 查看存储的值")
        print("输入 'M' 存储当前结果到内存")
        print("输入 'R' 读取内存中的值\n")

        while True:
            try:
                expression = input(">>> ")

                if expression.lower() == 'exit':
                    break
                elif expression.lower() == 'history':
                    for record in self.history[-10:]:  # 显示最近10条
                        print(f"  {record}")
                elif expression.lower() == 'memory':
                    print(f"内存: {self.memory}")
                elif expression == 'M':
                    if self.history:
                        self.memory = float(self.history[-1].split(' = ')[1])
                        print(f"已存储: {self.memory}")
                    else:
                        print("没有可存储的结果")
                elif expression == 'R':
                    print(f"内存: {self.memory}")
                else:
                    result = self.evaluate(expression)
                    print(f"= {result}")

            except ValueError as e:
                print(f"错误: {e}")
            except KeyboardInterrupt:
                print("\n再见!")
                break
            except Exception as e:
                print(f"错误: {e}")

# 运行
if __name__ == "__main__":
    calc = CommandLineCalculator()
    calc.run()

扩展功能

1. 添加科学计算功能

def scientific_calculate(self, expression):
    """科学计算"""
    import math

    # 替换函数名
    expression = expression.replace('sin', 'math.sin')
    expression = expression.replace('cos', 'math.cos')
    expression = expression.replace('tan', 'math.tan')
    expression = expression.replace('log', 'math.log10')
    expression = expression.replace('ln', 'math.log')
    expression = expression.replace('sqrt', 'math.sqrt')
    expression = expression.replace('exp', 'math.exp')
    expression = expression.replace('pi', 'math.pi')
    expression = expression.replace('e', 'math.e')

    return self.evaluate(expression)

2. 添加函数绘图功能

import matplotlib.pyplot as plt
import numpy as np

def plot_function(self, function_str, x_range=(-10, 10), points=1000):
    """绘制函数图像"""
    try:
        # 创建x值
        x = np.linspace(x_range[0], x_range[1], points)

        # 计算y值
        y = []
        for xi in x:
            try:
                # 替换x变量
                expr = function_str.replace('x', str(xi))
                yi = eval(expr)
                y.append(yi)
            except:
                y.append(np.nan)

        # 绘制图像
        plt.figure(figsize=(10, 6))
        plt.plot(x, y, label=f'y = {function_str}')
        plt.axhline(y=0, color='k', linestyle='-', linewidth=0.5)
        plt.axvline(x=0, color='k', linestyle='-', linewidth=0.5)
        plt.grid(True, linestyle='--', alpha=0.6)
        plt.xlabel('x')
        plt.ylabel('y')
        plt.title(f'函数图像: y = {function_str}')
        plt.legend()
        plt.show()

    except Exception as e:
        print(f"绘图错误: {e}")

# 使用示例
# plot_function('x**2')  # 绘制抛物线
# plot_function('math.sin(x)')  # 绘制正弦函数
# plot_function('math.exp(-x**2)')  # 绘制高斯函数

3. 添加进制转换功能

def convert_base(number, from_base, to_base):
    """进制转换"""
    # 将任意进制转换为十进制
    decimal = int(str(number), from_base)

    # 将十进制转换为目标进制
    if to_base == 2:
        return bin(decimal)[2:]
    elif to_base == 8:
        return oct(decimal)[2:]
    elif to_base == 10:
        return str(decimal)
    elif to_base == 16:
        return hex(decimal)[2:].upper()
    else:
        raise ValueError("不支持的进制")

# 使用示例
# convert_base(255, 10, 2)  # 十进制255转二进制: '11111111'
# convert_base('FF', 16, 10)  # 十六进制FF转十进制: '255'
# convert_base('1010', 2, 16)  # 二进制1010转十六进制: 'A'

测试用例

import unittest

class TestCalculator(unittest.TestCase):
    """计算器测试"""

    def setUp(self):
        self.calc = Calculator(None)

    def test_basic_operations(self):
        """测试基本运算"""
        self.assertEqual(self.calc.evaluate("1+1"), 2)
        self.assertEqual(self.calc.evaluate("2*3"), 6)
        self.assertEqual(self.calc.evaluate("10-5"), 5)
        self.assertEqual(self.calc.evaluate("8/2"), 4)

    def test_operator_precedence(self):
        """测试运算符优先级"""
        self.assertEqual(self.calc.evaluate("2+3*4"), 14)
        self.assertEqual(self.calc.evaluate("(2+3)*4"), 20)
        self.assertEqual(self.calc.evaluate("10-2*3"), 4)

    def test_decimal_operations(self):
        """测试小数运算"""
        self.assertAlmostEqual(self.calc.evaluate("1.5+2.5"), 4.0)
        self.assertAlmostEqual(self.calc.evaluate("3.14*2"), 6.28)

    def test_error_handling(self):
        """测试错误处理"""
        with self.assertRaises(ValueError):
            self.calc.evaluate("1/0")

        with self.assertRaises(ValueError):
            self.calc.evaluate("1+")

if __name__ == '__main__':
    unittest.main()

使用说明

GUI版本

  1. 运行程序:python calculator_gui.py
  2. 点击按钮输入表达式
  3. 按 "=" 计算结果
  4. 按 "C" 清除输入
  5. 按 "M" 存储结果到内存

命令行版本

  1. 运行程序:python calculator_cli.py
  2. 输入表达式并回车
  3. 输入 "exit" 退出
  4. 输入 "history" 查看历史

扩展建议

  1. 添加更多科学函数:反三角函数、双曲函数等
  2. 支持变量定义:定义变量和函数
  3. 添加主题切换:深色/浅色主题
  4. 支持键盘输入:使用键盘快捷键
  5. 添加单位转换:长度、重量、温度等
  6. 支持复数运算:复数四则运算
  7. 添加历史记录保存:将历史保存到文件
  8. 支持表达式解析:解析和显示中间结果

学习要点

通过这个项目,你将学习到:

  1. GUI编程:tkinter的基本使用
  2. 事件处理:按钮点击、键盘事件
  3. 字符串处理:表达式解析
  4. 异常处理:错误检测和处理
  5. 数据结构:列表、字典的使用
  6. 模块化编程:代码组织和复用
  7. 测试驱动开发:编写测试用例

相关资源


开始动手吧! 从简单的功能开始,逐步添加更多特性。祝你编程愉快!