用 Python 30 分钟做出自己的记事本

作者:忘忧记日期:2026/2/8


🌟 《零基础手把手:用 Python 30 分钟做出自己的记事本》

—— 不是照抄代码,而是理解每行代码的「灵魂」


🧩 第一步:为什么我们需要「基础窗口」?(新手必懂!)

❌ 常见错误:直接写 window.show() 但窗口不显示?

✅ 正确逻辑:程序运行流程图

启动程序

创建应用对象

创建窗口

显示窗口

进入事件循环

📝 代码详解(逐行解释):

1import sys  # 必须!用于接收系统参数(比如文件路径)
2from PyQt6.QtWidgets import QApplication, QMainWindow  # 从PyQt库导入两个核心组件
3
4# 1️⃣ 创建应用对象(灵魂!所有PyQt程序必须有)
5app = QApplication(sys.argv)  # sys.argv = 系统传递的命令行参数(比如打开的文件名)
6
7# 2️⃣ 创建主窗口(你的「画布」)
8window = QMainWindow()  # 100% 会用到的窗口类型
9window.setWindowTitle("我的迷你记事本")  # 设置标题(为什么叫"窗口"?因为它是所有UI的容器!)
10window.resize(500, 400)  # 设置大小(宽500px, 高400px)
11
12# 3️⃣ 显示窗口(关键!)
13window.show()  #  为什么必须写?因为窗口默认是「隐形」的!
14#  如果不写这行,程序会直接结束,你永远看不到窗口!
15
16# 4️⃣ 进入事件循环(让程序「活」起来)
17sys.exit(app.exec())  #  为什么叫「事件循环」?因为程序会一直监听用户操作(点击、输入等)
18

💡 思考题

❓ 如果我删掉 sys.exit(app.exec()) 会怎样?
答案:程序会立刻结束!你只能看到一闪而过的窗口(就像打开一个网页立刻关闭)。


🧩 第二步:为什么需要 QTextEdit?(小白必看!)

❌ 常见错误:用 QLineEdit(单行输入框)做记事本 → 无法输入多行文字!

✅ 正确选择:QTextEdit = 专为多行文本设计的编辑器

📝 代码详解(添加编辑器):

1from PyQt6.QtWidgets import QApplication, QMainWindow, QTextEdit  # 新增导入
2
3app = QApplication(sys.argv)
4window = QMainWindow()
5window.setWindowTitle("我的迷你记事本")
6window.resize(700, 500)  # 宽高调大一点(记事本需要更多空间)
7
8#  关键步骤:把编辑器设置为窗口的「中央内容」
9editor = QTextEdit()  # 创建编辑器对象
10window.setCentralWidget(editor)  # 设置为窗口的主区域
11
12#  为什么叫「中央」?因为窗口可以有顶部菜单、底部状态栏,编辑器占中间大块
13

💡 思考题

❓ 如果我把 window.setCentralWidget(editor) 写成 window.setWidget(editor) 会怎样?
答案:报错!setCentralWidget 是 QMainWindow 的专属方法(PyQt 设计的规范)。


🧩 第三步:菜单栏怎么设计?(不是复制粘贴!)

❌ 常见错误:直接写 menubar = menuBar() → 无菜单!

✅ 正确逻辑:菜单 = 1个主菜单 + 多个子菜单项

📝 代码详解(分步构建菜单):

1# 1️⃣ 创建菜单栏(必须先有窗口才有菜单栏)
2menubar = window.menuBar()  # 获取窗口的菜单栏对象
3
4# 2️⃣ 添加「文件」主菜单(为什么叫「主菜单」?因为它在最左边)
5file_menu = menubar.addMenu("文件")  # 创建菜单项
6
7# 3️⃣ 在「文件」里添加子菜单(每个子菜单是用户点击的按钮)
8open_action = file_menu.addAction("打开文件")  # 添加「打开」按钮
9save_action = file_menu.addAction("保存文件")  # 添加「保存」按钮
10
11# 4️⃣ 设置快捷键(为什么需要?因为用户习惯Ctrl+S!)
12save_action.setShortcut("Ctrl+S")  # 40% 用户会用快捷键
13

💡 思考题

❓ 为什么「打开」和「保存」要用 addAction 而不是 addButton
答案QAction 是 PyQt 的统一事件处理单元(不是按钮!),它能绑定到菜单/按钮/工具栏。


🧩 第四步:实现「打开文件」功能(关键!)

❌ 常见错误:忘记 with open → 文件没关闭 → 内存泄漏!

✅ 正确逻辑:用户点击 → 弹出文件选择框 → 读取文件 → 显示到编辑器

📝 代码详解(带思考注释):

1def open_file(self):
2    #  为什么用 QFileDialog.getOpenFileName?因为这是PyQt的标准文件选择器
3    path, _ = QFileDialog.getOpenFileName(
4        self,  # 父窗口(确保弹窗在记事本内部)
5        "打开文件",  # 窗口标题
6        "",  # 默认打开路径(留空表示当前目录)
7        "Text Files (*.txt);;All Files (*)"  # 文件过滤器(显示.txt和所有文件)
8    )
9    
10    #  为什么需要检查 path?因为用户可能点了「取消」!
11    if path:
12        #  为什么用 'r'  utf-8?因为读取文本文件,且支持中文
13        with open(path, 'r', encoding='utf-8') as f:
14            self.editor.setText(f.read())  # 将文件内容放入编辑器
15        
16        #  为什么更新状态栏?让用户知道发生了什么
17        self.statusBar().showMessage(f"已打开: {path}")
18

💡 思考题

❓ 如果用户打开一个 .jpg 文件会怎样?
答案:程序会崩溃!因为 f.read() 读的是二进制,不是文本。这就是为什么需要文件过滤器"Text Files (*.txt)")。


🧩 第五步:实现「字数统计」(小白最爱的功能!)

❌ 常见错误:用 self.editor.text() → 会包含HTML标签!

✅ 正确逻辑:实时监听文字变化 → 计算纯文本字数

📝 代码详解(关键在 textChanged 信号):

1# 1️⃣ 初始化状态栏(在 initUI 里)
2self.statusBar().showMessage("准备就绪")  # 初始状态
3
4# 2️⃣ 监听文字变化(核心!)
5self.editor.textChanged.connect(self.update_status)  # 当文字变化时,自动调用 update_status
6
7# 3️⃣ 实现统计函数
8def update_status(self):
9    text = self.editor.toPlainText()  #  为什么用 toPlainText?因为这是纯文本!
10    char_count = len(text)  # 字符数(含空格)
11    line_count = len(text.split('\n'))  # 行数(按换行符分割)
12    
13    # 更新状态栏(为什么用 f-string?因为可读性高!)
14    self.statusBar().showMessage(f"字数: {char_count} | 行数: {line_count}")
15

💡 思考题

❓ 为什么 text.split('\n') 能算行数?如果用户用的是 \r\n(Windows换行符)会怎样?
答案split('\n') 会把 \r\n 当成两个字符,导致行数多1。正确做法:用 text.splitlines()(PyQt内置方法,自动处理换行符)。


🧩 第六步:为什么需要「类结构」?(小白的终极困惑)

❌ 常见错误:把所有代码写在全局(导致变量混乱!)

✅ 正确逻辑:用类封装所有功能 → 代码清晰 + 方便绑定槽函数

📝 为什么这样写?

1class SimplePad(QMainWindow):  # 继承自QMainWindow(让记事本拥有窗口能力)
2    def __init__(self):
3        super().__init__()  # 初始化父类(必须!)
4        self.initUI()  # 自己写的初始化方法
5
6    def initUI(self):
7        # 这里放所有界面初始化代码(窗口、编辑器、菜单等)
8        #  为什么不用全局变量?因为:1. 代码可读性高 2. 便于多人协作 3. 避免变量覆盖
9

💡 思考题

❓ 如果我删掉 super().__init__() 会怎样?
答案:窗口会报错!因为 QMainWindow 的初始化逻辑没执行(PyQt要求必须调用父类构造函数)。


✅ 最终代码(完整版,带详细注释)

1import sys
2from PyQt6.QtWidgets import QApplication, QMainWindow, QTextEdit, QFileDialog, QFontDialog, QMenuBar, QStatusBar
3
4class SimplePad(QMainWindow):
5    def __init__(self):
6        super().__init__()  # 1️⃣ 必须初始化父类
7        self.initUI()  # 2️⃣ 初始化界面
8
9    def initUI(self):
10        # 🔧 1. 设置窗口基础属性
11        self.setWindowTitle("SimplePad - 我的第一个记事本")
12        self.resize(800, 600)  # 更大的窗口(方便看字数)
13
14        # 📝 2. 创建文本编辑器(核心!)
15        self.editor = QTextEdit()
16        self.setCentralWidget(self.editor)  # 设置为窗口中央内容
17
18        # 📋 3. 创建菜单栏(分步骤构建)
19        menubar = self.menuBar()  # 获取菜单栏
20        
21        # 文件菜单
22        file_menu = menubar.addMenu("文件")
23        file_menu.addAction("新建").triggered.connect(self.editor.clear)  # 新建  清空
24        file_menu.addAction("打开").triggered.connect(self.open_file)  # 打开  绑定函数
25        save_action = file_menu.addAction("保存")
26        save_action.setShortcut("Ctrl+S")  # 快捷键
27        save_action.triggered.connect(self.save_file)  # 保存  绑定函数
28
29        # 格式菜单
30        format_menu = menubar.addMenu("格式")
31        format_menu.addAction("字体设置").triggered.connect(self.change_font)
32
33        # 📊 4. 状态栏(字数统计)
34        self.statusBar().showMessage("准备就绪")
35        self.editor.textChanged.connect(self.update_status)  # 关键!监听文字变化
36
37    # ⚙️ 5. 打开文件(带错误处理)
38    def open_file(self):
39        path, _ = QFileDialog.getOpenFileName(
40            self, "打开文件", "", "Text Files (*.txt);;All Files (*)"
41        )
42        if path:  #  检查用户是否选择了文件
43            with open(path, 'r', encoding='utf-8') as f:
44                self.editor.setText(f.read())
45            self.statusBar().showMessage(f"已打开: {path}")
46
47    # 📦 6. 保存文件(带用户友好提示)
48    def save_file(self):
49        path, _ = QFileDialog.getSaveFileName(
50            self, "保存文件", "", "Text Files (*.txt)"
51        )
52        if path:
53            content = self.editor.toPlainText()  #  纯文本!
54            with open(path, 'w', encoding='utf-8') as f:
55                f.write(content)
56            self.statusBar().showMessage(f"已保存: {path}")
57
58    # 🎨 7. 字体设置(用标准对话框)
59    def change_font(self):
60        font, ok = QFontDialog.getFont()
61        if ok:
62            self.editor.setFont(font)
63
64    # 📊 8. 字数统计(实时更新!)
65    def update_status(self):
66        text = self.editor.toPlainText()
67        char_count = len(text)
68        line_count = len(text.splitlines())  #  正确处理换行符!
69        self.statusBar().showMessage(f"字数: {char_count} | 行数: {line_count}")
70
71# 🚀 启动程序
72if __name__ == "__main__":
73    app = QApplication(sys.argv)
74    window = SimplePad()
75    window.show()
76    sys.exit(app.exec())
77


用 Python 30 分钟做出自己的记事本》 是转载文章,点击查看原文


相关推荐


type-challenges(ts类型体操): 10 - 元组转合集
fxss2026/1/30

10 - 元组转合集 by Anthony Fu (@antfu) #中等 #infer #tuple #union 题目 实现泛型TupleToUnion<T>,它返回元组所有值的合集。 例如 type Arr = ['1', '2', '3'] type Test = TupleToUnion<Arr> // expected to be '1' | '2' | '3' 在 Github 上查看:tsch.js.org/10/zh-CN 代码 /* _____________ 你的代


LeetCode 377 组合总和 Ⅳ
展菲2026/1/21

文章目录 摘要描述题解答案题解代码分析1. 动态规划的基本思路2. 初始状态3. 状态转移方程4. 为什么这样能计算出排列个数?5. 与组合问题的区别6. 优化:避免不必要的计算 示例测试及结果示例 1:nums = [1,2,3], target = 4示例 2:nums = [9], target = 3示例 3:nums = [1,2], target = 3示例 4:nums = [1], target = 1 时间复杂度空间复杂度进阶问题:如果数组中含有负数问题分析解决方


HarmonyOS一杯冰美式的时间 -- UIUtils基础功能
猫猫头啊2026/1/13

一、前言 最近在写状态管理相关的代码,发现 HarmonyOS 的 UIUtils 这个工具类还挺实用的。它主要解决一些状态管理框架在使用过程中遇到的边界问题,比如代理对象、V1/V2 混用、数据绑定这些场景。 今天顺手整理一下它的几个核心功能,方便以后查。 该系列依旧会带着大家,了解,开阔一些不怎么热门的API,也可能是偷偷被更新的API,也可以是好玩的,藏在官方文档的边边角角~当然也会有一些API,之前是我们辛辛苦苦的手撸代码,现在有一个API能帮我们快速实现的,希望大家能找宝藏。 如果您有


Elasticsearch 8.13.4 动态同义词实战全解析
detayun2026/1/4

在搜索引擎的江湖里,“词不达意"往往是阻碍用户找到心仪内容的最后一道鸿沟。当用户搜索"番茄"时,如果你的库里只有"西红柿"和"圣女果”,传统的精确匹配只能让用户空手而归。同义词库,便是那把填补语义裂痕的钥匙。然而,在 Elasticsearch 8.13.4 这个版本中,我们不再满足于重启服务来更新词库的"笨办法",我们要的是如丝般顺滑的动态热更新。 今天,我们就来一场技术突围,深度剖析在 ES 8.13.4 时代,如何玩转动态同义词,让你的搜索引擎拥有"自我进化"的灵魂。 一、 告别"文件搬运


卷积神经网络CNN
代码洲学长2025/12/26

CNN简介 卷积神经网络就是一个包括卷积层和池化层的神经网络,主要应用于计算机视觉方面,应用场景包括图像分类、目标检测、面部解锁、自动驾驶等。 整体架构流程 CNN的主要结构为 输入层,隐藏层 和输出层,主体架构主要体现在隐藏层中的网络,依次为卷积层 池化层 然后全连接层直接输出。CNN分别进行了两场卷积和池化 ,最终通过三个全连接层进行输出。 卷积层结构图 input(32, 32, 3) conv(3, 3, 6) relu(30, 30, 6) pool(2, 2, 6)


设计模式——责任链模式实战,优雅处理Kafka消息
KD2025/12/18

一、业务背景 Kafka接收消息,需要A,B,C...多种策略做处理,再通过http请求发送给下游。多种策略混在一起很难维护,通过责任链模式把每种策略的代码收敛到自己的Handler中 二、具体设计 classDiagram Handler <|-- StrategyAHandler Handler <|-- StrategyBHandler Handler <|-- UploadHandler Handler: +void handleUserData(UserContext, Handler


【Perfetto从入门到精通】2. 使用 Perfetto 追踪/分析 APP 的 Native/Java 内存
Lei_official2025/12/10

这个世界就是这样,你从失败中学到的东西可能比成功中学到的东西更多——《Android 传奇》 说起 Android APP 内存分析,我们第一时间想到的,往往是 Android Studio Profiler、MAT 这样的老牌工具,而 Perfetto 的出现,又为其提供了一种更加贴近底层的视角。而且相比于现有的工具,Perfetto 更加擅长于分析 Native 内存占用,可以说是补齐了工程师在这方面的短板。 在内存方向,我计划用2~3篇文章来介绍 Perfetto 的功能、特点、使用方


前端高频面试题之CSS篇(二)
程序员小寒2025/11/30

1、如何实现两栏布局? 两栏布局指的是左边宽度固定,右边宽度自适应。 DOM 结构如下: <body> <div class="box"> <div class="left"></div> <div class="right"></div> </div> </body> 1.1 利用 flex 布局实现 实现思路:将父元素设为 flex 布局,左边元素宽度固定,右边元素设为 flex: 1,即自适应。 .box { display: flex; width: 5


丰田正在使用 Flutter 开发游戏引擎 Fluorite
恋猫de小郭2026/2/17

近日,丰田汽车旗下子公司丰田互联北美公司宣布,即将开源基于 Flutter 的自主研发的游戏引擎 Flourite ,而实际上在此之前,Flutter 已经是丰田车机的开发 SDK 之一。 Toyota Connected North America,TCNA,是丰田的北美子公司,专注于车载软件、AI 等。 Fluorite 是首款完全集成 Flutter 的主机级(console-grade)游戏引擎,主要针对车载数字座舱(digital cockpit)和嵌入式低端硬件设计,已在 202


【深度学习基础篇04】从回归到分类:图像分类与卷积神经网络入门
ppppppatrick2026/2/25

【深度学习基础篇】从回归到分类:图像分类与卷积神经网络入门 文章目录 【深度学习基础篇】从回归到分类:图像分类与卷积神经网络入门一、前情提要:从回归实战到分类任务的核心转变回归与分类的核心区别:输出逻辑的本质不同分类任务的输出解码:从“置信度”到“类别标签” 二、图像分类的前提:理解图像的张量表示1. 图像的核心维度:通道×高度×宽度(C×H×W)2. 批量图像的张量格式:N×C×H×W3. 全连接层处理图像的痛点:维度爆炸 三、卷积的核心概念:从“局部感知”到特征提取1

首页编辑器站点地图

本站内容在 CC BY-SA 4.0 协议下发布

Copyright © 2026 XYZ博客