你有没有遇到过这种场景——正在专心写代码或者处理报表,突然需要算点东西。打开系统自带的计算器,发现它只是个“小学生水平”的工具,开个根号都要切到科学模式,更别提算三角函数或者做单位换算了。你叹了口气,要么打开网页搜一个在线计算器,忍受一堆广告的轰炸,要么干脆手算。
我当时的想法很简单:为什么不自己写一个?
反正天天跟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(400, 500)
# 显示屏
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呼出,算完自动把结果复制到剪贴板。这个功能实现起来也就十来行代码,但每天都能省下几十秒,一年算下来是个不小的数字。
好的工具不是买来的,是自己造的。当你需要的时候,动手写一个就是最好的选择。