Python使用PyQt5打造桌面應(yīng)用的入門指南
1. 引言:為什么選擇PyQt5開(kāi)發(fā)桌面應(yīng)用
當(dāng)你掌握了Python基礎(chǔ)語(yǔ)法后,自然會(huì)想要?jiǎng)?chuàng)造一些有界面的實(shí)用工具。這時(shí)候,GUI(圖形用戶界面)開(kāi)發(fā)就成為了必經(jīng)之路。在眾多Python GUI框架中,PyQt5以其強(qiáng)大的功能和優(yōu)雅的設(shè)計(jì)脫穎而出。
PyQt5是Qt框架的Python綁定,Qt本身是一個(gè)久經(jīng)考驗(yàn)的跨平臺(tái)C++應(yīng)用程序框架。通過(guò)PyQt5,我們可以用Python語(yǔ)言享受到Qt的所有功能,而無(wú)需與C++的復(fù)雜性打交道。
PyQt5的核心優(yōu)勢(shì)
- 功能全面:從簡(jiǎn)單的窗口到復(fù)雜的3D圖形,應(yīng)有盡有
- 真正的跨平臺(tái):同一份代碼可在Windows、macOS、Linux上運(yùn)行
- Pythonic的優(yōu)雅:既有Qt的強(qiáng)大,又有Python的簡(jiǎn)潔
- 豐富的控件庫(kù):按鈕、表格、樹(shù)形視圖等上百種控件
- 活躍的社區(qū):遇到問(wèn)題容易找到解決方案
適合的應(yīng)用場(chǎng)景
- 數(shù)據(jù)分析可視化工具
- 日常工作效率工具
- 原型快速開(kāi)發(fā)
- 中小型企業(yè)應(yīng)用
2. PyQt5與PySide,以及PyQt6簡(jiǎn)介
在開(kāi)始之前,你可能會(huì)遇到幾個(gè)相似的名字,這里簡(jiǎn)單澄清一下:
PyQt5 vs PySide2:
兩者都是Qt的Python綁定,功能幾乎完全相同。主要區(qū)別在于:
- 許可證:PyQt5使用GPL/commercial,PySide2使用LGPL(對(duì)商業(yè)應(yīng)用更友好)
- 歷史:PyQt出現(xiàn)較早,PySide是Qt官方后來(lái)推出的
- API細(xì)微差別:方法名略有不同,如信號(hào)連接方式
PyQt6的登場(chǎng):
PyQt6是PyQt5的下一代版本,主要變化包括:
- 默認(rèn)使用Qt6庫(kù)(PyQt5使用Qt5)
- 一些API發(fā)生了變化和優(yōu)化
- 移除了對(duì)Python 2的支持
為什么本文選擇PyQt5?
對(duì)于初學(xué)者,PyQt5有更豐富的教程資源、更穩(wěn)定的環(huán)境,而且目前大多數(shù)項(xiàng)目仍在使用PyQt5。掌握了PyQt5,遷移到PyQt6或PySide6也會(huì)很容易。
3. 快速開(kāi)始:環(huán)境搭建
使用pip安裝(推薦大多數(shù)用戶)
pip install PyQt5
使用uv安裝(新一代Python包管理工具)
uv add pyqt5==5.15.11 pyqt5-qt5==5.15.2
這里限定版本是因?yàn)閜yqt5的最新版本不適配于Windows uv庫(kù),如果是其他設(shè)備可能不需要限定版本。
版本說(shuō)明
- PyQt5 5.15.x 是PyQt5的最后一個(gè)系列版本
- 支持Python 3.5及以上版本
- 建議使用Python 3.8+以獲得最佳體驗(yàn)
驗(yàn)證安裝
安裝完成后,可以運(yùn)行以下代碼驗(yàn)證:
import sys
from PyQt5.QtWidgets import QApplication, QLabel
app = QApplication(sys.argv)
label = QLabel("Hello PyQt5!")
label.setMinimumSize(400, 50)
label.show()
app.exec_()
運(yùn)行效果:

(由于電腦分辨率不同,具體的大小效果可能不同)
我運(yùn)行完代碼之后會(huì)打印這個(gè)信息:
Can't find filter element
Can't find filter element
不知道是為什么,但是好像也不影響GUI應(yīng)用的展示,我就先不管了……
4. 第一個(gè)PyQt5窗口:Hello World
讓我們從一個(gè)最簡(jiǎn)單的完整示例開(kāi)始:
import sys
from PyQt5.QtWidgets import QApplication, QWidget
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
# 設(shè)置窗口位置和大小
self.setGeometry(300, 300, 750, 500)
# 設(shè)置窗口標(biāo)題
self.setWindowTitle('我的第一個(gè)PyQt5應(yīng)用')
# 顯示窗口
self.show()
if __name__ == '__main__':
# 創(chuàng)建應(yīng)用實(shí)例
app = QApplication(sys.argv)
# 創(chuàng)建主窗口
window = MainWindow()
# 進(jìn)入主循環(huán)
sys.exit(app.exec_())
運(yùn)行效果:

代碼解析:
import sys:提供對(duì)系統(tǒng)相關(guān)功能的訪問(wèn)QApplication:每個(gè)PyQt5應(yīng)用都需要一個(gè)QApplication實(shí)例QWidget:所有用戶界面對(duì)象的基類self.setGeometry(x, y, width, height):設(shè)置窗口位置和大小app.exec_():?jiǎn)?dòng)應(yīng)用的事件循環(huán)
5. 理解PyQt5的核心"積木"
5.1 QApplication:應(yīng)用程序的心臟
QApplication是PyQt5應(yīng)用的"大腦"和"心臟",負(fù)責(zé):
- 管理應(yīng)用程序的控制流:協(xié)調(diào)各個(gè)窗口和控件的工作
- 處理系統(tǒng)級(jí)事件:接收和分發(fā)鼠標(biāo)點(diǎn)擊、鍵盤輸入、窗口重繪等事件
- 提供全局設(shè)置:管理應(yīng)用程序的字體、樣式、調(diào)色板等
重要規(guī)則:一個(gè)應(yīng)用只能有一個(gè)QApplication實(shí)例!
app = QApplication(sys.argv) # sys.argv用于接收命令行參數(shù)
事件循環(huán):GUI程序的"心臟跳動(dòng)"
當(dāng)我們運(yùn)行一個(gè)PyQt5程序時(shí),到底發(fā)生了什么?讓我用一個(gè)比喻來(lái)解釋:
想象一下,你的GUI程序就像一個(gè)餐廳:
- QApplication是餐廳經(jīng)理
- 事件循環(huán)(event loop)是餐廳的服務(wù)流程
- 用戶操作(點(diǎn)擊、輸入)就是顧客的訂單
為什么需要事件循環(huán)?
# 啟動(dòng)事件循環(huán) app.exec_()
app.exec_() 啟動(dòng)了一個(gè)無(wú)限循環(huán),這個(gè)循環(huán)不斷地做三件事:
- 監(jiān)聽(tīng)系統(tǒng)事件(鼠標(biāo)點(diǎn)擊、鍵盤輸入等)
- 將事件分發(fā)給對(duì)應(yīng)的控件處理
- 處理完事件后,等待下一個(gè)事件
這個(gè)循環(huán)會(huì)一直運(yùn)行,直到你關(guān)閉所有窗口或調(diào)用app.quit()。
sys.exit(app.exec_())到底是什么意思?
這是一個(gè)初學(xué)者常見(jiàn)的困惑點(diǎn)。讓我們分解來(lái)看:
sys.exit(app.exec_())
分解理解:
app.exec_():
- 啟動(dòng)事件循環(huán)
- 返回一個(gè)退出狀態(tài)碼(通常是0表示成功,非0表示錯(cuò)誤)
- 事件循環(huán)會(huì)阻塞在這里,直到程序退出
sys.exit():
- Python標(biāo)準(zhǔn)庫(kù)函數(shù)
- 用于退出Python程序
- 可以接收一個(gè)退出狀態(tài)碼作為參數(shù)
為什么要這樣寫?
# 不推薦的寫法 app.exec_() # 程序會(huì)在這里卡住,但退出時(shí)可能不會(huì)清理資源 # 推薦的寫法 sys.exit(app.exec_()) # 確保程序正確退出,并返回狀態(tài)碼
工作原理示意圖:
開(kāi)始
↓
創(chuàng)建 QApplication
↓
創(chuàng)建窗口和控件
↓
sys.exit(app.exec_())
├── 啟動(dòng)事件循環(huán)(app.exec_())
│ ├── 監(jiān)聽(tīng)用戶操作
│ ├── 處理事件
│ └── 等待下一個(gè)事件...
│
└── 事件循環(huán)結(jié)束時(shí)返回狀態(tài)碼
↓
sys.exit(狀態(tài)碼) 退出程序
完整示例:理解程序執(zhí)行流程
import sys
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton
class MyApp(QWidget):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('QApplication示例')
self.setGeometry(300, 300, 300, 200)
btn = QPushButton('退出程序', self)
btn.clicked.connect(self.close) # 點(diǎn)擊按鈕關(guān)閉窗口
btn.move(100, 80)
self.show()
if __name__ == '__main__':
print("1. 程序開(kāi)始")
app = QApplication(sys.argv)
print("2. QApplication實(shí)例已創(chuàng)建")
window = MyApp()
print("3. 窗口已創(chuàng)建并顯示")
print("4. 進(jìn)入事件循環(huán)...")
exit_code = app.exec_() # 在這里程序會(huì)"阻塞",等待事件
print(f"5. 事件循環(huán)結(jié)束,退出碼: {exit_code}")
sys.exit(exit_code)
運(yùn)行這個(gè)程序,你會(huì)看到:
- 控制臺(tái)先打印1-4步的信息
- 然后程序進(jìn)入事件循環(huán),GUI界面出現(xiàn)
- 當(dāng)你關(guān)閉窗口時(shí),事件循環(huán)結(jié)束
- 最后打印第5步的信息,程序退出
常見(jiàn)問(wèn)題解答
Q: 為什么要用sys.exit()包裝app.exec_()?
A: 為了確保程序正確退出,并返回合適的退出狀態(tài)碼給操作系統(tǒng)。
Q: 可以不用sys.exit()嗎?
A: 在簡(jiǎn)單程序中可能可以,但在復(fù)雜程序中,不使用sys.exit()可能導(dǎo)致資源未正確釋放。
Q: 什么時(shí)候事件循環(huán)會(huì)結(jié)束?
A: 當(dāng)調(diào)用app.quit()或關(guān)閉所有窗口時(shí),事件循環(huán)會(huì)結(jié)束。
Q: 事件循環(huán)期間,我的代碼還能運(yùn)行嗎?
A: 不能直接運(yùn)行。所有代碼都必須在事件處理函數(shù)中執(zhí)行。如果需要執(zhí)行長(zhǎng)時(shí)間任務(wù),應(yīng)該使用多線程。
記住這個(gè)模式
幾乎所有PyQt5程序都遵循這個(gè)模式:
import sys
from PyQt5.QtWidgets import QApplication, QWidget
def main():
app = QApplication(sys.argv) # 1. 創(chuàng)建應(yīng)用
window = QWidget() # 2. 創(chuàng)建窗口
window.show() # 3. 顯示窗口
return app.exec_() # 4. 進(jìn)入事件循環(huán)
if __name__ == '__main__':
sys.exit(main()) # 5. 確保正確退出
掌握了QApplication和事件循環(huán)的概念,你就理解了PyQt5程序運(yùn)行的基礎(chǔ)機(jī)制。這是構(gòu)建更復(fù)雜應(yīng)用的基石!
5.2 QWidget與QMainWindow:窗口的兩大家族
QWidget:所有可視化組件的基類
- 按鈕、標(biāo)簽、輸入框等都是QWidget的子類
- 可以單獨(dú)作為窗口使用
QMainWindow:帶有菜單欄、工具欄、狀態(tài)欄的主窗口
- 適用于復(fù)雜的應(yīng)用程序
- 提供了標(biāo)準(zhǔn)的應(yīng)用程序框架
QMainWindow是QWidget的"加強(qiáng)版",但它們的定位和用途有很大區(qū)別。
核心關(guān)系:繼承與擴(kuò)展
# PyQt5中的繼承關(guān)系
object
↓
QObject
↓
QWidget ← 所有可視化元素的基類
↓
QMainWindow ← 專門用于主窗口的特殊QWidget
簡(jiǎn)單來(lái)說(shuō):
- QWidget是所有可視控件的"爺爺輩"基類
- QMainWindow是QWidget的一個(gè)"大兒子",專門為應(yīng)用程序主窗口設(shè)計(jì)
- 按鈕、標(biāo)簽、輸入框等都是QWidget的其他"兒子孫子"
QWidget:萬(wàn)能的基礎(chǔ)窗口
QWidget是PyQt5中最基礎(chǔ)的窗口類,它可以扮演兩種角色:
1. 作為獨(dú)立的簡(jiǎn)單窗口
from PyQt5.QtWidgets import QWidget, QLabel, QPushButton, QVBoxLayout, QApplication
import sys
class SimpleWindow(QWidget):
def __init__(self):
super().__init__()
self.setWindowTitle("我是一個(gè)QWidget窗口")
self.resize(300, 200)
# 創(chuàng)建布局和控件
layout = QVBoxLayout()
label = QLabel("這是一個(gè)簡(jiǎn)單的對(duì)話框")
button = QPushButton("確定")
layout.addWidget(label)
layout.addWidget(button)
self.setLayout(layout)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = SimpleWindow()
window.show()
sys.exit(app.exec_())
運(yùn)行效果:

2. 作為其他控件的容器
# QLabel、QPushButton、QLineEdit等都是QWidget的子類
# 它們都繼承了QWidget的所有功能
label = QLabel("我是QWidget的子類")
button = QPushButton("我也是QWidget的子類")
QWidget的特點(diǎn):
- 輕量級(jí),內(nèi)存占用小
- 靈活,可以自由布局
- 適合對(duì)話框、彈窗、簡(jiǎn)單工具窗口
QMainWindow:專業(yè)的應(yīng)用程序主窗口
QMainWindow是專門為應(yīng)用程序主窗口設(shè)計(jì)的,它提供了"開(kāi)箱即用"的標(biāo)準(zhǔn)主窗口結(jié)構(gòu):
from PyQt5.QtWidgets import QApplication, QMainWindow, QTextEdit, QAction, QLabel
import sys
class MainAppWindow(QMainWindow):
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
# 1. 設(shè)置中央部件(必需)
text_edit = QTextEdit()
self.setCentralWidget(text_edit)
# 2. 創(chuàng)建菜單欄(可選)
menubar = self.menuBar()
file_menu = menubar.addMenu("文件(&F)")
# 向菜單添加具體的動(dòng)作
open_action = QAction("打開(kāi)", self)
save_action = QAction("保存", self)
exit_action = QAction("退出", self)
exit_action.triggered.connect(self.close)
file_menu.addAction(open_action)
file_menu.addAction(save_action)
file_menu.addSeparator()
file_menu.addAction(exit_action)
# 3. 創(chuàng)建工具欄并添加工具按鈕(必需,否則工具欄為空)
toolbar = self.addToolBar("標(biāo)準(zhǔn)工具欄")
# 添加工具欄按鈕(使用文本)
toolbar.addAction("新建")
toolbar.addAction("打開(kāi)")
toolbar.addAction("保存")
toolbar.addSeparator()
toolbar.addAction("打印")
# 4. 創(chuàng)建狀態(tài)欄(可選)
status_bar = self.statusBar()
status_bar.showMessage("就緒", 3000) # 顯示3秒
# 添加永久顯示的狀態(tài)欄部件
permanent_label = QLabel("永久狀態(tài)信息")
status_bar.addPermanentWidget(permanent_label)
# 5. 設(shè)置窗口屬性
self.setWindowTitle("文本編輯器")
self.setGeometry(100, 100, 800, 600)
self.show()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainAppWindow()
sys.exit(app.exec_())
運(yùn)行效果(前3秒會(huì)顯示就緒):

展開(kāi)菜單”文件“,過(guò)3秒:

QMainWindow的標(biāo)準(zhǔn)結(jié)構(gòu)
+---------------------------------------------------+
| 菜單欄 (Menu Bar) |
+---------------------------------------------------+
| 工具欄 (Tool Bar) |
+---------------------------------------------------+
| |
| 中央部件 (Central Widget) |
| |
| +-----------------------------------------+ |
| | | |
| | 你的主要內(nèi)容在這里 | |
| | | |
| +-----------------------------------------+ |
| |
+---------------------------------------------------+
| 狀態(tài)欄 (Status Bar) |
+---------------------------------------------------+
我們很容易發(fā)現(xiàn)QMainWindow里面又有菜單,又有工具欄,那我們很容易就會(huì)提問(wèn),不對(duì)啊,現(xiàn)在的軟件不是要么只有菜單(如VSCode),要么只有標(biāo)簽形式切換的工具欄(如Word)嗎?
這是一個(gè)相當(dāng)復(fù)雜的問(wèn)題,首先Word老版(1984-2006)其實(shí)真的是又有菜單欄又有工具欄的:
┌─────────────────────────────────────┐
│ 文件(F) 編輯(E) 視圖(V) 幫助(H) ← 固定菜單欄
├─────────────────────────────────────┤
│ [??] [??] [??] [???] [B] [I] [U] ← 浮動(dòng)工具欄
└─────────────────────────────────────┘
特點(diǎn):
- 菜單欄是固定的、層級(jí)式的
- 工具欄是獨(dú)立的、可拖動(dòng)的
- 功能隱藏在多層菜單中
- 空間利用率較低
但2007年后改為了采用這樣的Ribbon界面:
┌─────────────────────────────────────┐
│ ?? 插入 設(shè)計(jì) 布局 引用 郵件 審閱 視圖 ← 情境化標(biāo)簽頁(yè)
├─────────────────────────────────────┤
│ 當(dāng)前任務(wù)相關(guān)功能分組展示 ← 自適應(yīng)功能區(qū)
│ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ │ 剪貼板 │ │ 字體 │ │ 段落 │
│ │ │ │ │ │ │
│ └─────────┘ └─────────┘ └─────────┘
└─────────────────────────────────────┘
核心創(chuàng)新:
- 情境化標(biāo)簽頁(yè):根據(jù)當(dāng)前任務(wù)切換功能組
- 功能分組可視化:圖標(biāo)+文字,直觀易懂
- 自適應(yīng)空間:根據(jù)窗口大小調(diào)整顯示
- 減少菜單層級(jí):80%常用功能在第一層
而VSCode則采用的是側(cè)邊活動(dòng)欄:
# VS Code的界面結(jié)構(gòu)
┌─────────────────────────────────────────────────────┐
│ File Edit View Go Run Terminal Help │ ← 頂部菜單欄(簡(jiǎn)潔)
├─────────────────────────────────────────────────────┤
│ ?? ?? ?? ?? ?? │ ← 活動(dòng)欄(側(cè)邊圖標(biāo)欄)
│ │
│ 側(cè)邊面板區(qū)域 │
│ (資源管理器、搜索、Git等) │
│ │
├─────────────────────────────────────────────────────┤
│ [main.py] │ ← 編輯器標(biāo)簽頁(yè)
│ │
│ def main(): │ ← 主編輯區(qū)域
│ print("Hello World") │
│ │
├─────────────────────────────────────────────────────┤
│ Python 3.12.4 • UTF-8 • LF • 2 spaces │ ← 狀態(tài)欄(多信息顯示)
└─────────────────────────────────────────────────────┘
關(guān)鍵區(qū)別對(duì)比表
| 特性 | QWidget | QMainWindow |
|---|---|---|
| 定位 | 基礎(chǔ)窗口/控件 | 應(yīng)用程序主窗口 |
| 內(nèi)存占用 | 較小 | 較大(包含更多組件) |
| 內(nèi)置結(jié)構(gòu) | 無(wú) | 有標(biāo)準(zhǔn)菜單欄、工具欄、狀態(tài)欄區(qū)域 |
| 布局管理 | 需要手動(dòng)設(shè)置布局 | 中央部件區(qū)域可設(shè)置布局 |
| 靈活性 | 高,完全自定義 | 有一定結(jié)構(gòu)限制 |
| 典型用途 | 對(duì)話框、彈窗、簡(jiǎn)單窗口 | 軟件主窗口、復(fù)雜應(yīng)用 |
| 是否能使用布局管理器 | 可以,直接設(shè)置 | 只能對(duì)中央部件使用布局 |
| 是否能有多個(gè)實(shí)例 | 可以 | 通常只有一個(gè) |
關(guān)鍵注意事項(xiàng)
1. QMainWindow不能直接設(shè)置布局!
(布局的介紹見(jiàn)本文第6節(jié))
# ? 錯(cuò)誤做法
class WrongWindow(QMainWindow):
def __init__(self):
super().__init__()
layout = QVBoxLayout()
layout.addWidget(QLabel("測(cè)試"))
self.setLayout(layout) # 這不會(huì)工作!
# ? 正確做法
class CorrectWindow(QMainWindow):
def __init__(self):
super().__init__()
# 創(chuàng)建一個(gè)QWidget作為中央部件的容器
central_widget = QWidget()
self.setCentralWidget(central_widget)
# 在容器上設(shè)置布局
layout = QVBoxLayout()
layout.addWidget(QLabel("測(cè)試"))
central_widget.setLayout(layout) # 在中央部件上設(shè)置布局
2. 實(shí)際應(yīng)用中的選擇策略
# 場(chǎng)景1:需要標(biāo)準(zhǔn)菜單欄/工具欄的軟件 → 選擇QMainWindow
class TextEditor(QMainWindow):
"""文本編輯器,需要菜單欄保存文件"""
pass
# 場(chǎng)景2:簡(jiǎn)單的配置對(duì)話框 → 選擇QWidget
class SettingsDialog(QWidget):
"""設(shè)置對(duì)話框,不需要復(fù)雜的菜單結(jié)構(gòu)"""
pass
# 場(chǎng)景3:復(fù)雜應(yīng)用的子窗口 → 選擇QWidget
class PreviewWindow(QWidget):
"""預(yù)覽窗口,作為主窗口的子窗口"""
pass
3. 混合使用示例
from PyQt5.QtWidgets import QApplication, QMainWindow, QTextEdit, QWidget
import sys
class MainApp(QMainWindow):
def __init__(self):
super().__init__()
# 主窗口使用QMainWindow
self.setWindowTitle("主應(yīng)用程序")
self.setCentralWidget(QTextEdit())
# 但點(diǎn)擊按鈕可以彈出QWidget對(duì)話框
self.settings_dialog = SettingsDialog(self)
def show_settings(self):
self.settings_dialog.exec_() # 顯示模態(tài)對(duì)話框
class SettingsDialog(QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("設(shè)置")
self.resize(300, 200)
# 使用QWidget作為對(duì)話框
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainApp()
window.show()
sys.exit(app.exec_())
生成效果(我手動(dòng)拖拽了一下界面大小):

實(shí)踐建議
什么時(shí)候用QWidget?
- 對(duì)話框和彈窗:確認(rèn)框、消息框、設(shè)置窗口
- 簡(jiǎn)單工具:計(jì)算器、單位轉(zhuǎn)換器等小工具
- 自定義控件:創(chuàng)建可重用的界面組件
- 子窗口:主應(yīng)用中的浮動(dòng)面板
什么時(shí)候用QMainWindow?
- 應(yīng)用程序主窗口:編輯器、瀏覽器、IDE等
- 復(fù)雜應(yīng)用:需要標(biāo)準(zhǔn)菜單和工具欄的軟件
- 專業(yè)工具:圖像處理、數(shù)據(jù)分析等專業(yè)軟件
一個(gè)有用的技巧:從簡(jiǎn)單開(kāi)始
# 初期:從QWidget開(kāi)始,快速原型
class SimpleApp(QWidget):
def __init__(self):
super().__init__()
# 簡(jiǎn)單布局和功能
# 后期:需要更多功能時(shí),輕松遷移到QMainWindow
class EnhancedApp(QMainWindow):
def __init__(self):
super().__init__()
# 將原來(lái)的QWidget內(nèi)容設(shè)為中央部件
old_widget = SimpleApp()
self.setCentralWidget(old_widget)
# 添加菜單欄、工具欄等
總結(jié)
QWidget和QMainWindow不是"基礎(chǔ)版"和"高級(jí)版"的關(guān)系,而是不同用途的工具:
- QWidget像是白紙:給你最大自由度,想畫什么就畫什么
- QMainWindow像是已經(jīng)畫好框架的畫布:提供了標(biāo)準(zhǔn)結(jié)構(gòu),你只需要填充內(nèi)容
記住這個(gè)簡(jiǎn)單的選擇原則:
- 如果只需要一個(gè)簡(jiǎn)單的窗口或?qū)υ捒?→ 選QWidget
- 如果要?jiǎng)?chuàng)建有標(biāo)準(zhǔn)菜單/工具欄的應(yīng)用程序主窗口 → 選QMainWindow
理解它們的區(qū)別后,你就能根據(jù)實(shí)際需求做出合適的選擇,寫出更專業(yè)、更高效的PyQt5代碼!
5.3 信號(hào)與槽:Qt的"事件通信系統(tǒng)"
這是Qt最強(qiáng)大的特性之一!想象一下:
- 信號(hào)(Signal):像電燈的開(kāi)關(guān)
- 槽(Slot):像電燈本身
- 連接(Connect):像連接開(kāi)關(guān)和燈的電線
工作原理:事件發(fā)生 → 發(fā)出信號(hào) → 連接到槽 → 執(zhí)行函數(shù)
import sys
from PyQt5.QtWidgets import (
QApplication,
QWidget,
QVBoxLayout,
QLabel,
QPushButton,
QTextEdit,
)
from PyQt5.QtCore import pyqtSignal
class CustomButton(QPushButton):
"""
自定義按鈕類
演示如何創(chuàng)建和使用自定義信號(hào)
"""
# 自定義信號(hào) - 可以發(fā)送一個(gè)字符串
message_signal = pyqtSignal(str)
# 另一個(gè)自定義信號(hào) - 可以發(fā)送兩個(gè)整數(shù)
number_signal = pyqtSignal(int, int)
def __init__(self, text, parent=None):
super().__init__(text, parent)
self.click_count = 0
def mousePressEvent(self, event):
"""
重寫鼠標(biāo)按下事件
每次點(diǎn)擊都會(huì)發(fā)出兩個(gè)信號(hào)
"""
self.click_count += 1
# 發(fā)出第一個(gè)信號(hào) - 帶字符串消息
self.message_signal.emit(f"第{self.click_count}次點(diǎn)擊!")
# 發(fā)出第二個(gè)信號(hào) - 帶兩個(gè)數(shù)字
x, y = event.x(), event.y()
self.number_signal.emit(x, y)
# 重要:調(diào)用父類方法確保正常行為
super().mousePressEvent(event)
class ExampleApp(QWidget):
def __init__(self):
super().__init__()
self.setup_ui()
def setup_ui(self):
self.setWindowTitle("自定義信號(hào)詳細(xì)示例")
self.resize(500, 400)
# 創(chuàng)建布局
layout = QVBoxLayout()
# 1. 信息顯示區(qū)域
self.info_label = QLabel("點(diǎn)擊下面的按鈕查看效果", self)
layout.addWidget(self.info_label)
# 2. 文本顯示區(qū)域(用于顯示詳細(xì)信息)
self.text_display = QTextEdit(self)
self.text_display.setReadOnly(True)
layout.addWidget(self.text_display)
# 3. 創(chuàng)建自定義按鈕
self.custom_btn = CustomButton("自定義按鈕 - 點(diǎn)擊我", self)
layout.addWidget(self.custom_btn)
# 4. 重置按鈕
self.reset_btn = QPushButton("重置計(jì)數(shù)", self)
layout.addWidget(self.reset_btn)
self.setLayout(layout)
# 連接信號(hào)
self.connect_signals()
def connect_signals(self):
"""連接所有信號(hào)到槽函數(shù)"""
# 連接自定義按鈕的第一個(gè)信號(hào)
self.custom_btn.message_signal.connect(self.update_info)
# 連接自定義按鈕的第二個(gè)信號(hào)
self.custom_btn.number_signal.connect(self.show_click_position)
# 連接重置按鈕
self.reset_btn.clicked.connect(self.reset_counter)
def update_info(self, message):
"""更新信息標(biāo)簽"""
self.info_label.setText(f"自定義信號(hào): {message}")
self.text_display.append(f"收到消息: {message}")
def show_click_position(self, x, y):
"""顯示點(diǎn)擊位置"""
self.text_display.append(f"點(diǎn)擊位置: x={x}, y={y}")
def reset_counter(self):
"""重置計(jì)數(shù)器"""
self.custom_btn.click_count = 0
self.info_label.setText("計(jì)數(shù)器已重置")
self.text_display.append("--- 計(jì)數(shù)器重置 ---")
# 運(yùn)行示例
if __name__ == "__main__":
app = QApplication(sys.argv)
window = ExampleApp()
window.show()
sys.exit(app.exec_())
首先設(shè)置帶信號(hào)(代碼里設(shè)置了兩種)的按鈕,然后將按鈕放到組件上,把信號(hào)連接到槽函數(shù)上(槽函數(shù)也可以是一個(gè)lambda函數(shù))。按鈕被觸發(fā)時(shí),信號(hào)發(fā)射(emit),槽函數(shù)接受并處理信號(hào),在代碼中就將處理結(jié)果展示在界面上:


5.4 常見(jiàn)控件快速上手
from PyQt5.QtWidgets import (
QLabel, # 標(biāo)簽 - 顯示文本或圖片
QPushButton, # 按鈕 - 點(diǎn)擊觸發(fā)動(dòng)作
QLineEdit, # 單行輸入框
QTextEdit, # 多行文本編輯
QCheckBox, # 復(fù)選框
QRadioButton, # 單選按鈕
QComboBox, # 下拉框
QSpinBox, # 數(shù)字輸入框
QProgressBar, # 進(jìn)度條
QSlider, # 滑塊
)
6. 讓界面自動(dòng)排列:布局管理
沒(méi)有布局管理器的GUI就像沒(méi)有CSS的HTML——元素會(huì)堆疊在一起。
布局管理器其實(shí)就是把一堆空間按布局組合到一起。
為什么需要布局管理器
- 自動(dòng)調(diào)整控件位置和大小
- 適應(yīng)不同分辨率和窗口大小
- 簡(jiǎn)化界面設(shè)計(jì)
常用布局管理器
QVBoxLayout - 垂直排列
from PyQt5.QtWidgets import QVBoxLayout, QPushButton, QLabel
layout = QVBoxLayout()
layout.addWidget(QLabel("第一行"))
layout.addWidget(QPushButton("第二行"))
layout.addWidget(QLabel("第三行"))
self.setLayout(layout) # 應(yīng)用到窗口
QHBoxLayout - 水平排列
from PyQt5.QtWidgets import QHBoxLayout
layout = QHBoxLayout()
layout.addWidget(QPushButton("左"))
layout.addWidget(QPushButton("中"))
layout.addWidget(QPushButton("右"))
布局嵌套 - 創(chuàng)建復(fù)雜界面
# 創(chuàng)建主垂直布局
main_layout = QVBoxLayout()
# 創(chuàng)建水平布局并添加控件
top_layout = QHBoxLayout()
top_layout.addWidget(QLabel("姓名:"))
top_layout.addWidget(QLineEdit())
# 將水平布局添加到垂直布局
main_layout.addLayout(top_layout)
main_layout.addWidget(QPushButton("提交"))
self.setLayout(main_layout)
7. 初學(xué)者常見(jiàn)錯(cuò)誤與注意事項(xiàng)
錯(cuò)誤1:忘記創(chuàng)建QApplication實(shí)例
# 錯(cuò)誤寫法 window = QWidget() window.show() # 正確寫法 app = QApplication(sys.argv) window = QWidget() window.show() app.exec_()
錯(cuò)誤2:在子線程中直接更新GUI
在PyQt5(以及大多數(shù)GUI框架)中,有一個(gè)黃金規(guī)則:所有GUI操作都必須在主線程(也稱為GUI線程)中進(jìn)行!
為什么有這個(gè)限制?
簡(jiǎn)化的解釋:
PyQt5的GUI組件不是"線程安全"的
想象一下兩個(gè)線程同時(shí)修改同一個(gè)控件:
線程A: label.setText("Hello") 線程B: label.setText("World")
↓ ↓
同時(shí)寫入同一個(gè)內(nèi)存區(qū)域 → 數(shù)據(jù)競(jìng)爭(zhēng) → 程序崩潰!
錯(cuò)誤示例分析
錯(cuò)誤代碼:
import sys
import time
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel, QPushButton
from PyQt5.QtCore import QThread, pyqtSignal
class WorkerThread(QThread):
"""工作線程"""
def run(self):
time.sleep(2) # 模擬耗時(shí)操作
# ? 危險(xiǎn)!在子線程中直接更新GUI
label.setText("處理完成!") # 可能導(dǎo)致崩潰
# 在主線程中創(chuàng)建窗口
app = QApplication(sys.argv)
window = QWidget()
label = QLabel("等待中...")
button = QPushButton("開(kāi)始任務(wù)")
layout = QVBoxLayout()
layout.addWidget(label)
layout.addWidget(button)
window.setLayout(layout)
# 創(chuàng)建工作線程
worker = WorkerThread()
def start_task():
worker.start()
button.clicked.connect(start_task)
window.show()
sys.exit(app.exec_())
可能的結(jié)果:
- 程序可能直接崩潰
- 界面可能不更新
- 可能偶爾正常工作,但不可靠(最危險(xiǎn)的情況?。?/li>
正確解決方案:使用信號(hào)
方案1:QThread + moveToThread
import sys
import time
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel, QPushButton
from PyQt5.QtCore import QThread, pyqtSignal, QObject
class Worker(QObject):
"""工作對(duì)象,使用信號(hào)通信"""
# 定義信號(hào)
progress_signal = pyqtSignal(int) # 傳遞進(jìn)度百分比
result_signal = pyqtSignal(str) # 傳遞結(jié)果字符串
finished_signal = pyqtSignal() # 完成信號(hào)(無(wú)參數(shù))
def do_work(self):
"""執(zhí)行耗時(shí)任務(wù)"""
for i in range(1, 11):
time.sleep(0.5) # 模擬耗時(shí)操作
self.progress_signal.emit(i * 10) # 發(fā)射進(jìn)度信號(hào)
self.result_signal.emit("處理完成!") # 發(fā)射結(jié)果信號(hào)
self.finished_signal.emit() # 發(fā)射完成信號(hào)
class MainWindow(QWidget):
def __init__(self):
super().__init__()
self.init_ui()
self.setup_worker()
def init_ui(self):
self.setWindowTitle("線程安全更新GUI示例")
self.resize(400, 300)
layout = QVBoxLayout()
self.label = QLabel("準(zhǔn)備開(kāi)始任務(wù)...")
self.progress_label = QLabel("進(jìn)度: 0%")
self.button = QPushButton("開(kāi)始任務(wù)")
self.status_label = QLabel("狀態(tài): 空閑")
layout.addWidget(self.label)
layout.addWidget(self.progress_label)
layout.addWidget(self.status_label)
layout.addWidget(self.button)
self.setLayout(layout)
self.button.clicked.connect(self.start_work)
def setup_worker(self):
"""設(shè)置工作線程和信號(hào)連接"""
# 創(chuàng)建工作對(duì)象和線程
self.worker = Worker()
self.thread = QThread()
# 將工作對(duì)象移動(dòng)到線程中
self.worker.moveToThread(self.thread)
# 連接信號(hào)
self.worker.progress_signal.connect(self.update_progress)
self.worker.result_signal.connect(self.update_result)
self.worker.finished_signal.connect(self.work_finished)
# 線程開(kāi)始后,連接do_work方法
self.thread.started.connect(self.worker.do_work)
# 線程結(jié)束時(shí),清理資源
self.worker.finished_signal.connect(self.thread.quit)
self.worker.finished_signal.connect(self.worker.deleteLater)
self.thread.finished.connect(self.thread.deleteLater)
def start_work(self):
"""開(kāi)始工作"""
self.button.setEnabled(False)
self.status_label.setText("狀態(tài): 處理中...")
self.thread.start()
def update_progress(self, progress):
"""更新進(jìn)度(在主線程中執(zhí)行)"""
self.progress_label.setText(f"進(jìn)度: {progress}%")
def update_result(self, result):
"""更新結(jié)果(在主線程中執(zhí)行)"""
self.label.setText(result)
def work_finished(self):
"""任務(wù)完成(在主線程中執(zhí)行)"""
self.button.setEnabled(True)
self.status_label.setText("狀態(tài): 完成")
if __name__ == "__main__":
app = QApplication(sys.argv)
window = MainWindow()
window.show()
sys.exit(app.exec_())
方案2:QRunnable
需要管理生命周期:
import sys
import time
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QLabel, QPushButton
from PyQt5.QtCore import QThreadPool, QRunnable, pyqtSignal, QObject, pyqtSlot, QMutex
class WorkerSignals(QObject):
"""定義工作線程的信號(hào)"""
finished = pyqtSignal()
progress = pyqtSignal(int)
result = pyqtSignal(str)
def __init__(self):
super().__init__()
self.is_valid = True # 添加有效性標(biāo)志
def delete_later(self):
self.is_valid = False
self.deleteLater()
class Worker(QRunnable):
"""工作線程類"""
def __init__(self):
super().__init__()
self.signals = WorkerSignals()
self.mutex = QMutex() # 互斥鎖保護(hù)信號(hào)對(duì)象
def run(self):
"""執(zhí)行耗時(shí)任務(wù)"""
for i in range(10):
time.sleep(0.3)
progress = (i + 1) * 10
# 使用互斥鎖保護(hù)信號(hào)對(duì)象
self.mutex.lock()
try:
if (
self.signals
and hasattr(self.signals, "is_valid")
and self.signals.is_valid
):
self.signals.progress.emit(progress)
except RuntimeError:
# 信號(hào)對(duì)象已被刪除
pass
finally:
self.mutex.unlock()
self.mutex.lock()
try:
if (
self.signals
and hasattr(self.signals, "is_valid")
and self.signals.is_valid
):
self.signals.result.emit("任務(wù)完成!")
self.signals.finished.emit()
except RuntimeError:
pass
finally:
self.mutex.unlock()
class SimpleThreadExample(QWidget):
def __init__(self):
super().__init__()
self.init_ui()
self.threadpool = QThreadPool()
self.workers = [] # 保持對(duì)worker的引用
def init_ui(self):
self.setWindowTitle("簡(jiǎn)單多線程示例")
self.resize(300, 200)
layout = QVBoxLayout()
self.label = QLabel("點(diǎn)擊按鈕開(kāi)始任務(wù)")
self.button = QPushButton("開(kāi)始耗時(shí)任務(wù)")
layout.addWidget(self.label)
layout.addWidget(self.button)
self.setLayout(layout)
self.button.clicked.connect(self.start_task)
def start_task(self):
"""啟動(dòng)工作線程"""
# 禁用按鈕,防止重復(fù)點(diǎn)擊
self.button.setEnabled(False)
# 創(chuàng)建工作對(duì)象
worker = Worker()
self.workers.append(worker) # 保持引用
# 連接信號(hào)
worker.signals.progress.connect(self.on_progress)
worker.signals.result.connect(self.on_result)
worker.signals.finished.connect(lambda: self.on_finished(worker))
# 在線程池中執(zhí)行
self.threadpool.start(worker)
def on_progress(self, progress):
"""更新進(jìn)度(自動(dòng)在主線程中執(zhí)行)"""
self.label.setText(f"處理中... {progress}%")
def on_result(self, result):
"""顯示結(jié)果(自動(dòng)在主線程中執(zhí)行)"""
self.label.setText(result)
def on_finished(self, worker):
"""任務(wù)完成(自動(dòng)在主線程中執(zhí)行)"""
self.button.setEnabled(True)
# 清理worker
if worker in self.workers:
if worker.signals:
worker.signals.delete_later()
self.workers.remove(worker)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = SimpleThreadExample()
window.show()
sys.exit(app.exec_())
為什么信號(hào)能安全地更新GUI?
信號(hào)與槽的線程安全機(jī)制:
PyQt5的內(nèi)部機(jī)制:
當(dāng)信號(hào)從子線程發(fā)射時(shí),PyQt5會(huì)自動(dòng):
- 將信號(hào)放入主線程的事件隊(duì)列
- 主線程在適當(dāng)?shù)臅r(shí)候處理這個(gè)事件
- 調(diào)用連接的槽函數(shù)(在主線程中?。?/li>
所以:
worker.signals.result.emit("數(shù)據(jù)") # 子線程中發(fā)射信號(hào)
↓
PyQt5內(nèi)部:跨線程傳遞信號(hào)
↓
label.setText("數(shù)據(jù)") # 在主線程中執(zhí)行槽函數(shù)
其他安全更新GUI的方法
1. 使用QTimer在主線程中輪詢
from PyQt5.QtCore import QTimer
class SafeUpdateExample:
def __init__(self):
self.results_queue = [] # 線程安全的數(shù)據(jù)結(jié)構(gòu)
# 定時(shí)器在主線程中運(yùn)行
self.timer = QTimer()
self.timer.timeout.connect(self.check_results)
self.timer.start(100) # 每100毫秒檢查一次
def check_results(self):
"""在主線程中檢查并更新GUI"""
if self.results_queue:
result = self.results_queue.pop(0)
label.setText(result)
2. 使用QMetaObject.invokeMethod
from PyQt5.QtCore import QMetaObject, Qt, pyqtSlot
class WorkerThread(QThread):
result_ready = pyqtSignal(str)
def run(self):
# 耗時(shí)任務(wù)
result = "處理完成"
# 安全地調(diào)用主線程的方法
QMetaObject.invokeMethod(
main_window, # 目標(biāo)對(duì)象
"update_label", # 方法名
Qt.QueuedConnection, # 異步連接
result # 參數(shù)
)
class MainWindow:
@pyqtSlot(str)
def update_label(self, text):
label.setText(text) # 在主線程中執(zhí)行
實(shí)際應(yīng)用場(chǎng)景
場(chǎng)景1:網(wǎng)絡(luò)請(qǐng)求
class DownloadWorker(QThread):
progress = pyqtSignal(int)
finished = pyqtSignal(bytes)
error = pyqtSignal(str)
def run(self):
try:
# 下載文件(耗時(shí)操作)
for chunk in download_file():
self.progress.emit(chunk.progress)
self.finished.emit(file_data)
except Exception as e:
self.error.emit(str(e)) # 發(fā)送錯(cuò)誤信號(hào),而不是直接彈窗
場(chǎng)景2:數(shù)據(jù)處理
class DataProcessorWorker(QObject):
data_processed = pyqtSignal(pd.DataFrame) # 發(fā)送處理后的數(shù)據(jù)
error_occurred = pyqtSignal(str)
def process_large_data(self, data):
try:
# 復(fù)雜的數(shù)據(jù)處理
result = heavy_computation(data)
self.data_processed.emit(result)
except Exception as e:
self.error_occurred.emit(f"處理失敗: {e}")
常見(jiàn)錯(cuò)誤模式
錯(cuò)誤1:忘記moveToThread
worker = Worker() thread = QThread() # ? 忘記移動(dòng)對(duì)象到線程 thread.start() worker.do_work() # 還在主線程中執(zhí)行!
錯(cuò)誤2:直接調(diào)用GUI方法
def worker_function():
# 各種計(jì)算...
window.update_ui(data) # ? 危險(xiǎn)!在子線程中調(diào)用GUI方法
錯(cuò)誤3:忽略異常處理
def worker_function():
try:
# 可能失敗的操作
except Exception as e:
# ? 不要直接顯示錯(cuò)誤對(duì)話框
QMessageBox.critical(None, "錯(cuò)誤", str(e)) # 可能崩潰!
# ? 應(yīng)該發(fā)送信號(hào)
self.error_signal.emit(str(e))
最佳實(shí)踐總結(jié)
- 永遠(yuǎn)不在子線程中直接操作GUI控件
- 使用信號(hào)/槽進(jìn)行線程間通信
- 復(fù)雜的計(jì)算放在工作線程中
- GUI更新只在主線程中進(jìn)行
- 使用合適的錯(cuò)誤處理機(jī)制
- 使用
moveToThread()確保對(duì)象在正確的線程中
簡(jiǎn)單記憶法則
記住這句話:“信號(hào)發(fā)射是自由的,但槽函數(shù)執(zhí)行總是在主線程的懷抱中。”
這樣,您就能安全地在PyQt5中使用多線程了!
錯(cuò)誤3:內(nèi)存泄漏(忘記設(shè)置父對(duì)象)
# 可能的內(nèi)存泄漏
def create_widget():
widget = QWidget() # 沒(méi)有父對(duì)象,需要手動(dòng)管理
return widget
# 更好的做法
def create_widget(parent=None):
widget = QWidget(parent) # 指定父對(duì)象,自動(dòng)管理內(nèi)存
return widget
重要注意事項(xiàng)
- 所有GUI操作必須在主線程
- 使用布局管理器,而不是固定坐標(biāo)
- 合理使用信號(hào)與槽,避免過(guò)度耦合
- 學(xué)習(xí)使用Qt Designer進(jìn)行可視化設(shè)計(jì)
9. 總結(jié)
通過(guò)本文,你已經(jīng)掌握了PyQt5的基礎(chǔ)知識(shí):
- 理解了PyQt5的基本架構(gòu)
- 學(xué)會(huì)了創(chuàng)建窗口和基本控件
- 掌握了信號(hào)與槽機(jī)制
- 能夠使用布局管理器排列控件
- 創(chuàng)建了第一個(gè)交互式應(yīng)用
PyQt5的學(xué)習(xí)曲線可能有些陡峭,但一旦掌握,你將擁有創(chuàng)建強(qiáng)大桌面應(yīng)用的能力。記住,最好的學(xué)習(xí)方式就是動(dòng)手實(shí)踐。從今天開(kāi)始,嘗試用PyQt5解決你遇到的實(shí)際問(wèn)題吧!
到此這篇關(guān)于Python使用PyQt5打造桌面應(yīng)用的入門指南的文章就介紹到這了,更多相關(guān)Python PyQt5桌面應(yīng)用開(kāi)發(fā)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Python使用random.shuffle()隨機(jī)打亂字典排序
本文主要介紹了Python使用random.shuffle()隨機(jī)打亂字典排序,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08
python3利用smtplib通過(guò)qq郵箱發(fā)送郵件方法示例
python實(shí)現(xiàn)郵件發(fā)送較為簡(jiǎn)單,主要用到smtplib這個(gè)模塊,所以下面這篇文章主要給大家介紹了關(guān)于python3利用smtplib通過(guò)qq郵箱發(fā)送郵件的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來(lái)一起看看吧。2017-12-12
Python監(jiān)控系統(tǒng)資源(CPU/內(nèi)存/日志/磁盤/進(jìn)程)的完整指南
這篇文章主要為大家詳細(xì)介紹了Python監(jiān)控系統(tǒng)資源的相關(guān)方法,例如CPU,內(nèi)存,日志,磁盤和進(jìn)程,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2025-12-12
python實(shí)現(xiàn)自動(dòng)清理重復(fù)文件
這篇文章主要介紹了python如何實(shí)現(xiàn)自動(dòng)清理重復(fù)文件,幫助大家更好的理解和學(xué)習(xí)python,感興趣的朋友可以了解下2020-08-08
Django 解決阿里云部署同步數(shù)據(jù)庫(kù)報(bào)錯(cuò)的問(wèn)題
這篇文章主要介紹了Django 解決阿里云部署同步數(shù)據(jù)庫(kù)報(bào)錯(cuò)的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-05-05
運(yùn)用PyTorch動(dòng)手搭建一個(gè)共享單車預(yù)測(cè)器
這篇文章主要介紹了運(yùn)用PyTorch動(dòng)手搭建一個(gè)共享單車預(yù)測(cè)器,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-08-08
Django中信號(hào)signals的簡(jiǎn)單使用方法
這篇文章主要給大家介紹了關(guān)于Django中信號(hào)signals的簡(jiǎn)單使用方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Django具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07
Python實(shí)現(xiàn)多個(gè)圓和圓中圓的檢測(cè)
這篇文章主要為大家詳細(xì)介紹了Python如何實(shí)現(xiàn)多個(gè)圓檢測(cè)和圓中圓的檢測(cè),文中的實(shí)現(xiàn)方法講解詳細(xì),具有一定的借鑒價(jià)值,需要的可以參考一下2022-11-11
Pytorch加載數(shù)據(jù)集的方式總結(jié)及補(bǔ)充
Pytorch自定義數(shù)據(jù)集方法,應(yīng)該是用pytorch做算法的最基本的東西,下面這篇文章主要給大家介紹了關(guān)于Pytorch加載數(shù)據(jù)集的方式總結(jié)及補(bǔ)充,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-11-11

