PyQt5打造高级多功能计算器

6 阅读6分钟

你有没有遇到过这种场景——正在专心写代码或者处理报表,突然需要算点东西。打开系统自带的计算器,发现它只是个“小学生水平”的工具,开个根号都要切到科学模式,更别提算三角函数或者做单位换算了。你叹了口气,要么打开网页搜一个在线计算器,忍受一堆广告的轰炸,要么干脆手算。

我当时的想法很简单:为什么不自己写一个?

反正天天跟Python打交道,PyQt5这框架也听说过不少次。与其让别人的工具打断工作流,不如亲手打造一个趁手的“兵器”。这篇文章就是我折腾出来的成果——一个能当主力工具用的多功能计算器,顺便聊聊踩过的那些坑。

从界面开始,像搭积木一样摆按钮

写GUI程序的第一步永远是搭界面。PyQt5有个好处,可视化工具Qt Designer可以直接拖拖拽拽把框架搭好,不过我习惯手写代码——这样以后改样式、加功能心里更有数。

界面布局其实挺像摆积木。最上面放一个QLCDNumber作为显示屏,要那种老式计算器的数字风格,看着舒服。下面用QGridLayout把按钮排成网格,四行四列不够就加行,数字、运算符、功能键各就各位。

颜色我做了区分:数字按钮白底蓝字,运算符灰底黑字,AC(清空)用红色,等号用绿色。这种视觉分组能让操作更快,不用仔细看就知道哪个区域是干嘛的。

from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
import sys
import math

class Calculator(QMainWindow):
    def __init__(self):
        super().__init__()
        self.setWindowTitle("多功能计算器")
        self.setFixedSize(400500)
        
        # 显示屏
        self.display = QLCDNumber()
        self.display.setDigitCount(15)
        self.display.setSegmentStyle(QLCDNumber.Flat)
        
        # 按钮布局
        self.create_buttons()

这里有个小技巧:显示屏的digitCount设15位够用了,真要算天文数字也不会用这个计算器。setSegmentStyle让数字显示更现代一点,老式凸起风格有点过时。

按钮动起来,信号与槽的灵魂所在

PyQt最核心的机制叫“信号与槽”,听起来高大上,说白了就是“点一下按钮,做一件事”。每个按钮被点击时发出clicked信号,我们把这个信号连接到对应的处理函数上。

def create_buttons(self):
    buttons = [
        ['7''8''9''/'],
        ['4''5''6''*'],
        ['1''2''3''-'],
        ['0''.''=''+'],
        ['C''√''^''AC']
    ]
    
    grid = QGridLayout()
    central_widget = QWidget()
    central_widget.setLayout(grid)
    self.setCentralWidget(central_widget)
    
    # 把显示屏和按钮网格拼在一起
    main_layout = QVBoxLayout()
    main_layout.addWidget(self.display)
    main_layout.addLayout(grid)
    central_widget.setLayout(main_layout)
    
    for row, row_buttons in enumerate(buttons):
        for col, btn_text in enumerate(row_buttons):
            btn = QPushButton(btn_text)
            btn.clicked.connect(self.on_button_clicked)
            grid.addWidget(btn, row, col)

上面代码里的connect就是关键。不管按了哪个按钮,都交给同一个on_button_clicked函数处理,通过按钮上的文字来判断要做什么。

计算逻辑,比想象中要麻烦一点

一开始我图省事,直接用Python的eval函数计算表达式。这玩意儿确实方便,你给它字符串"2+3*4",它真能算出14。但eval有个大问题——不安全。恶意输入可能执行系统命令,虽然计算器场景风险不高,但这习惯不好。

再说用户体验。eval计算时没有实时反馈,你按完一连串数字和运算符,最后按等号才出结果。更好的做法是维护两个状态:当前输入的数字和等待执行的运算。

def on_button_clicked(self):
    text = self.sender().text()
    
    if text.isdigit() or text == '.':
        # 数字或小数点,拼接到当前输入
        self.current_input += text
        self.display.display(self.current_input)
        
    elif text in ['+''-''*''/']:
        # 运算符,保存当前数字和运算符
        self.last_number = float(self.current_input)
        self.current_operator = text
        self.current_input = ''
        
    elif text == '=':
        # 计算结果
        current = float(self.current_input)
        if self.current_operator == '+':
            result = self.last_number + current
        elif self.current_operator == '-':
            result = self.last_number - current
        # ... 其他运算
        self.display.display(result)
        self.current_input = str(result)

这种状态机的方式让计算器更“懂”你在做什么。按完“2+3”,显示屏清空让你输第二个数,最后按等号才出结果——这就是计算器该有的样子。

进阶功能,让计算器真正“高级”起来

光有四则运算称不上“高级”。科学计算器该有的东西得加上:三角函数、对数、开方、幂运算。

def calculate_function(self, func_name):
    value = float(self.current_input)
    
    if func_name == 'sqrt':
        result = math.sqrt(value)
    elif func_name == 'sin':
        # 角度转弧度
        result = math.sin(math.radians(value))  
    elif func_name == 'cos':
        result = math.cos(math.radians(value))
    elif func_name == 'log':
        result = math.log10(value)
    elif func_name == 'ln':
        result = math.log(value)
    # ...
    
    self.display.display(result)
    self.current_input = str(result)

这里用Python标准库的math模块就够了,不需要额外安装东西。三角函数的坑是角度和弧度转换,很多人第一次用math.sin(90)发现不是1,就是因为传了角度进去,但函数要的是弧度。

还有个实用的功能是单位换算。长度、重量、温度之间的转换其实都是乘个系数,用字典存起来就行。比如长度换算:{'m':1, 'cm':0.01, 'km':1000, 'inch':0.0254},用户选“100 cm in m”这种格式,解析出来一乘一除就完事。

键盘支持,真正的效率提升

一个只能用鼠标点的计算器算不上趁手。加上键盘监听,让数字小键盘也能操作。

def keyPressEvent(self, event):
    key = event.key()
    
    if key in [Qt.Key_0, Qt.Key_1, ..., Qt.Key_9]:
        self.current_input += chr(key)
    elif key == Qt.Key_Plus:
        self.set_operator('+')
    elif key == Qt.Key_Return or key == Qt.Key_Enter:
        self.calculate_result()
    elif key == Qt.Key_Backspace:
        self.current_input = self.current_input[:-1]
    elif key == Qt.Key_Escape:
        self.clear_all()
    
    self.display.display(self.current_input)

重写keyPressEvent方法后,用户甚至不需要碰鼠标。右手操作数字小键盘,左手辅助,这效率比系统计算器高太多了。

踩坑记录,给后来人的避坑指南

Qt Designer确实能拖出界面,但生成的.ui文件要转成.py,改一次转一次,特别烦。不如手写代码,版本控制也方便。

布局管理器用QVBoxLayout和QGridLayout组合,别用绝对坐标place()。不同屏幕分辨率下,写死的坐标会让界面乱掉。布局管理器会自动适配,省心。

除法要处理除零错误,开方不能开负数。这些边界条件不处理,程序一崩溃用户体验直接归零。try-except包一下,弹个提示框就行。

try:
    result = self.last_number / current
except ZeroDivisionError:
    QMessageBox.warning(self, "错误""除数不能为零")
    return

为什么需要自己的计算器

市面上计算器那么多,为什么非要自己写一个?因为没有人比你更懂你的工作流。

你可能经常需要把计算结果复制到某个特定格式里,或者要记录每一次计算的历史,又或者要用到某个冷门的物理常数。这些需求通用软件不会帮你实现,但自己的工具可以。代码就在那里,想加什么功能随时改。

写完之后我把这个计算器设成了快捷键启动,Ctrl+Alt+C呼出,算完自动把结果复制到剪贴板。这个功能实现起来也就十来行代码,但每天都能省下几十秒,一年算下来是个不小的数字。

好的工具不是买来的,是自己造的。当你需要的时候,动手写一个就是最好的选择。