基于Python+PyQt+MVVM方式開發(fā)桌面應(yīng)用
很多 Python 開發(fā)者第一次寫 PyQt 桌面應(yīng)用時(shí),代碼很容易變成這樣:
- 按鈕點(diǎn)擊邏輯寫在窗口類里。
- 數(shù)據(jù)處理也寫在窗口類里。
- 數(shù)據(jù)校驗(yàn)、狀態(tài)刷新、列表更新都寫在窗口類里。
- 項(xiàng)目越寫越大,最后
MainWindow變成幾千行。
小 demo 這樣寫沒問題,但如果你要做一個(gè)長期維護(hù)的桌面應(yīng)用,比如數(shù)據(jù)處理工具、內(nèi)部管理軟件、設(shè)備控制客戶端、圖像處理工具,就需要更清晰的代碼組織方式。
這篇文章用一個(gè)簡(jiǎn)單的待辦事項(xiàng)小工具,帶大家入門 Python + PyQt6 + MVVM 的開發(fā)方式。
說明:本文使用 PyQt6 演示。根據(jù) PyPI 頁面,PyQt6 是 Qt6 的 Python 綁定,截至 2026-04-22 可見的最新版本為 6.11.0,安裝要求 Python >=3.10。PyQt6 使用 GPL v3 或商業(yè)授權(quán),正式商業(yè)項(xiàng)目請(qǐng)?zhí)崆按_認(rèn)授權(quán)。
一、什么是 MVVM
MVVM 是 Model - View - ViewModel 的縮寫。
在桌面應(yīng)用中,可以這樣理解:
| 層 | 負(fù)責(zé)什么 | 不應(yīng)該負(fù)責(zé)什么 |
|---|---|---|
| Model | 業(yè)務(wù)數(shù)據(jù)、業(yè)務(wù)規(guī)則、數(shù)據(jù)結(jié)構(gòu) | 不關(guān)心按鈕、窗口、控件 |
| View | 窗口、按鈕、輸入框、列表等界面控件 | 不寫復(fù)雜業(yè)務(wù)邏輯 |
| ViewModel | 連接 View 和 Model,處理命令、狀態(tài)、校驗(yàn)、通知界面刷新 | 不直接依賴具體界面布局 |
簡(jiǎn)單說:
Model關(guān)心“數(shù)據(jù)是什么”。View關(guān)心“界面長什么樣”。ViewModel關(guān)心“界面操作后應(yīng)該怎么改變數(shù)據(jù)和狀態(tài)”。
二、PyQt 里為什么適合用 MVVM
Qt 本身有兩個(gè)非常重要的機(jī)制:
Signals / Slots:信號(hào)和槽,用來做對(duì)象之間的通信。Model / View:模型視圖機(jī)制,用來把數(shù)據(jù)和顯示控件分離。
Qt 官方文檔中提到,Model/View 架構(gòu)用于管理數(shù)據(jù)與顯示方式之間的關(guān)系,數(shù)據(jù)變化可以通過信號(hào)通知視圖刷新。Signals/Slots 則是 Qt 對(duì)象之間通信的核心機(jī)制。
這兩個(gè)機(jī)制和 MVVM 很契合:
- View 點(diǎn)擊按鈕,調(diào)用 ViewModel 的方法。
- ViewModel 修改 Model。
- ViewModel 通過信號(hào)通知 View 更新狀態(tài)。
- View 使用 Qt 的 item model 顯示列表、表格、樹。
需要注意的是,Qt 里的 Model/View 和 MVVM 里的 Model 不是一回事:
- MVVM 的
Model是業(yè)務(wù)模型,比如TodoItem、User、Order。 - Qt 的
QStandardItemModel、QAbstractListModel是給界面控件顯示數(shù)據(jù)用的模型。
初學(xué)時(shí)先把這兩件事區(qū)分開,后面架構(gòu)會(huì)清晰很多。
三、本文要做的演示程序
我們做一個(gè)簡(jiǎn)單的待辦事項(xiàng)桌面應(yīng)用:
功能包括:
- 輸入任務(wù)名稱。
- 點(diǎn)擊按鈕添加任務(wù)。
- 在列表中顯示任務(wù)。
- 雙擊任務(wù)切換完成狀態(tài)。
- 選中任務(wù)后可以刪除。
- 底部顯示統(tǒng)計(jì)信息。
- 輸入為空時(shí)給出提示。
這個(gè)功能不復(fù)雜,但足夠演示 MVVM 的基本分層。
四、項(xiàng)目結(jié)構(gòu)
建議使用下面的目錄結(jié)構(gòu):
pyqt_mvvm_demo/
requirements.txt
main.py
models/
__init__.py
todo_item.py
viewmodels/
__init__.py
todo_list_view_model.py
views/
__init__.py
main_window.py每個(gè)目錄的職責(zé):
| 文件 | 職責(zé) |
|---|---|
models/todo_item.py | 定義業(yè)務(wù)數(shù)據(jù)結(jié)構(gòu) |
viewmodels/todo_list_view_model.py | 管理任務(wù)列表、處理添加/刪除/切換完成狀態(tài) |
views/main_window.py | 創(chuàng)建窗口和控件,綁定 ViewModel |
main.py | 程序入口 |
五、安裝 PyQt6
先創(chuàng)建虛擬環(huán)境:
python -m venv .venv
Windows 激活:
.venv\Scripts\activate
macOS / Linux 激活:
source .venv/bin/activate
安裝 PyQt6:
pip install PyQt6
requirements.txt:
PyQt6>=6.11.0
如果你的環(huán)境暫時(shí)不能安裝最新版本,也可以先不鎖版本:
PyQt6
六、編寫 Model:業(yè)務(wù)數(shù)據(jù)
創(chuàng)建 models/todo_item.py:
from dataclasses import dataclass
@dataclass
class TodoItem:
title: str
done: bool = False
這個(gè)類非常簡(jiǎn)單,只表達(dá)業(yè)務(wù)數(shù)據(jù):
title:任務(wù)標(biāo)題。done:是否完成。
它不關(guān)心界面,也不依賴 PyQt。
這就是 MVVM 里 Model 層應(yīng)該保持的狀態(tài):干凈、簡(jiǎn)單、可測(cè)試。
七、編寫 ViewModel:業(yè)務(wù)狀態(tài)和界面命令
創(chuàng)建 viewmodels/todo_list_view_model.py:
from PyQt6.QtCore import QObject, Qt, pyqtSignal
from PyQt6.QtGui import QStandardItem, QStandardItemModel
from models.todo_item import TodoItem
class TodoListViewModel(QObject):
message_changed = pyqtSignal(str)
stats_changed = pyqtSignal(str)
def __init__(self):
super().__init__()
self._items: list[TodoItem] = []
self.list_model = QStandardItemModel()
self.list_model.itemChanged.connect(self._on_item_changed)
self._update_stats()
def add_task(self, title: str) -> bool:
title = title.strip()
if not title:
self.message_changed.emit("任務(wù)名稱不能為空")
return False
item = TodoItem(title=title)
self._items.append(item)
self._append_item_to_view_model(item)
self.message_changed.emit(f"已添加任務(wù):{title}")
self._update_stats()
return True
def remove_task(self, row: int) -> None:
if row < 0 or row >= len(self._items):
self.message_changed.emit("請(qǐng)先選擇要?jiǎng)h除的任務(wù)")
return
removed = self._items.pop(row)
self.list_model.removeRow(row)
self.message_changed.emit(f"已刪除任務(wù):{removed.title}")
self._update_stats()
def toggle_task(self, row: int) -> None:
if row < 0 or row >= len(self._items):
return
item = self._items[row]
item.done = not item.done
qt_item = self.list_model.item(row)
qt_item.setCheckState(
Qt.CheckState.Checked if item.done else Qt.CheckState.Unchecked
)
self.message_changed.emit(
f"已完成:{item.title}" if item.done else f"已取消完成:{item.title}"
)
self._update_stats()
def _append_item_to_view_model(self, todo: TodoItem) -> None:
qt_item = QStandardItem(todo.title)
qt_item.setEditable(False)
qt_item.setCheckable(True)
qt_item.setCheckState(
Qt.CheckState.Checked if todo.done else Qt.CheckState.Unchecked
)
qt_item.setData(todo, Qt.ItemDataRole.UserRole)
self.list_model.appendRow(qt_item)
def _on_item_changed(self, qt_item: QStandardItem) -> None:
row = qt_item.row()
if row < 0 or row >= len(self._items):
return
self._items[row].done = qt_item.checkState() == Qt.CheckState.Checked
self._update_stats()
def _update_stats(self) -> None:
total = len(self._items)
done = sum(1 for item in self._items if item.done)
pending = total - done
self.stats_changed.emit(f"總數(shù):{total},已完成:{done},未完成:{pending}")
這一層是本文最關(guān)鍵的部分。
ViewModel 做了幾件事:
- 保存業(yè)務(wù)數(shù)據(jù)列表:
self._items。 - 提供給界面顯示的 Qt 模型:
self.list_model。 - 提供界面可調(diào)用的命令:
add_task()、remove_task()、toggle_task()。 - 通過信號(hào)告訴界面狀態(tài)變化:
message_changed、stats_changed。
ViewModel 不應(yīng)該做的事:
- 不創(chuàng)建窗口。
- 不直接操作按鈕樣式。
- 不關(guān)心控件放在第幾行第幾列。
這樣以后即使 View 從 QWidget 改成 QML,或者從桌面窗口改成別的界面形式,很多業(yè)務(wù)邏輯仍然可以復(fù)用。
八、編寫 View:窗口和控件
創(chuàng)建 views/main_window.py:
from PyQt6.QtCore import QModelIndex
from PyQt6.QtWidgets import (
QHBoxLayout,
QLabel,
QLineEdit,
QListView,
QMainWindow,
QPushButton,
QVBoxLayout,
QWidget,
)
from viewmodels.todo_list_view_model import TodoListViewModel
class MainWindow(QMainWindow):
def __init__(self, view_model: TodoListViewModel):
super().__init__()
self.view_model = view_model
self.setWindowTitle("PyQt6 + MVVM 待辦事項(xiàng)")
self.resize(520, 360)
self.task_input = QLineEdit()
self.task_input.setPlaceholderText("輸入任務(wù)名稱")
self.add_button = QPushButton("添加")
self.remove_button = QPushButton("刪除選中")
self.remove_button.setEnabled(False)
self.todo_list = QListView()
self.todo_list.setModel(self.view_model.list_model)
self.message_label = QLabel("請(qǐng)輸入任務(wù)名稱,然后點(diǎn)擊添加")
self.stats_label = QLabel()
self._build_layout()
self._bind_events()
self._bind_view_model()
def _build_layout(self) -> None:
input_layout = QHBoxLayout()
input_layout.addWidget(self.task_input)
input_layout.addWidget(self.add_button)
button_layout = QHBoxLayout()
button_layout.addStretch()
button_layout.addWidget(self.remove_button)
root_layout = QVBoxLayout()
root_layout.addLayout(input_layout)
root_layout.addWidget(self.todo_list)
root_layout.addLayout(button_layout)
root_layout.addWidget(self.stats_label)
root_layout.addWidget(self.message_label)
container = QWidget()
container.setLayout(root_layout)
self.setCentralWidget(container)
def _bind_events(self) -> None:
self.add_button.clicked.connect(self._on_add_clicked)
self.task_input.returnPressed.connect(self._on_add_clicked)
self.remove_button.clicked.connect(self._on_remove_clicked)
self.todo_list.doubleClicked.connect(self._on_task_double_clicked)
self.todo_list.selectionModel().currentChanged.connect(self._on_selection_changed)
def _bind_view_model(self) -> None:
self.view_model.message_changed.connect(self.message_label.setText)
self.view_model.stats_changed.connect(self.stats_label.setText)
def _on_add_clicked(self) -> None:
ok = self.view_model.add_task(self.task_input.text())
if ok:
self.task_input.clear()
self.task_input.setFocus()
def _on_remove_clicked(self) -> None:
row = self.todo_list.currentIndex().row()
self.view_model.remove_task(row)
def _on_task_double_clicked(self, index: QModelIndex) -> None:
self.view_model.toggle_task(index.row())
def _on_selection_changed(self, current: QModelIndex) -> None:
self.remove_button.setEnabled(current.isValid())
View 層主要做三件事:
- 創(chuàng)建界面控件。
- 綁定控件事件。
- 綁定 ViewModel 的信號(hào)。
這里的窗口類沒有直接修改 _items,也沒有自己計(jì)算任務(wù)統(tǒng)計(jì)信息。它只負(fù)責(zé)把用戶操作轉(zhuǎn)交給 ViewModel。
這就是 MVVM 的核心思想:View 盡量薄,業(yè)務(wù)狀態(tài)放到 ViewModel。
九、編寫程序入口
創(chuàng)建 main.py:
import sys
from PyQt6.QtWidgets import QApplication
from viewmodels.todo_list_view_model import TodoListViewModel
from views.main_window import MainWindow
def main() -> int:
app = QApplication(sys.argv)
view_model = TodoListViewModel()
window = MainWindow(view_model)
window.show()
return app.exec()
if __name__ == "__main__":
raise SystemExit(main())
運(yùn)行:
python main.py
十、為了方便復(fù)制,這里給一個(gè)單文件版本
如果你只是想先跑起來,可以把下面代碼保存為 app.py。
import sys
from dataclasses import dataclass
from PyQt6.QtCore import QModelIndex, QObject, Qt, pyqtSignal
from PyQt6.QtGui import QStandardItem, QStandardItemModel
from PyQt6.QtWidgets import (
QApplication,
QHBoxLayout,
QLabel,
QLineEdit,
QListView,
QMainWindow,
QPushButton,
QVBoxLayout,
QWidget,
)
@dataclass
class TodoItem:
title: str
done: bool = False
class TodoListViewModel(QObject):
message_changed = pyqtSignal(str)
stats_changed = pyqtSignal(str)
def __init__(self):
super().__init__()
self._items: list[TodoItem] = []
self.list_model = QStandardItemModel()
self.list_model.itemChanged.connect(self._on_item_changed)
self._update_stats()
def add_task(self, title: str) -> bool:
title = title.strip()
if not title:
self.message_changed.emit("任務(wù)名稱不能為空")
return False
todo = TodoItem(title=title)
self._items.append(todo)
qt_item = QStandardItem(todo.title)
qt_item.setEditable(False)
qt_item.setCheckable(True)
qt_item.setCheckState(Qt.CheckState.Unchecked)
qt_item.setData(todo, Qt.ItemDataRole.UserRole)
self.list_model.appendRow(qt_item)
self.message_changed.emit(f"已添加任務(wù):{title}")
self._update_stats()
return True
def remove_task(self, row: int) -> None:
if row < 0 or row >= len(self._items):
self.message_changed.emit("請(qǐng)先選擇要?jiǎng)h除的任務(wù)")
return
removed = self._items.pop(row)
self.list_model.removeRow(row)
self.message_changed.emit(f"已刪除任務(wù):{removed.title}")
self._update_stats()
def toggle_task(self, row: int) -> None:
if row < 0 or row >= len(self._items):
return
todo = self._items[row]
todo.done = not todo.done
qt_item = self.list_model.item(row)
qt_item.setCheckState(
Qt.CheckState.Checked if todo.done else Qt.CheckState.Unchecked
)
self.message_changed.emit(
f"已完成:{todo.title}" if todo.done else f"已取消完成:{todo.title}"
)
self._update_stats()
def _on_item_changed(self, qt_item: QStandardItem) -> None:
row = qt_item.row()
if row < 0 or row >= len(self._items):
return
self._items[row].done = qt_item.checkState() == Qt.CheckState.Checked
self._update_stats()
def _update_stats(self) -> None:
total = len(self._items)
done = sum(1 for item in self._items if item.done)
pending = total - done
self.stats_changed.emit(f"總數(shù):{total},已完成:{done},未完成:{pending}")
class MainWindow(QMainWindow):
def __init__(self, view_model: TodoListViewModel):
super().__init__()
self.view_model = view_model
self.setWindowTitle("PyQt6 + MVVM 待辦事項(xiàng)")
self.resize(520, 360)
self.task_input = QLineEdit()
self.task_input.setPlaceholderText("輸入任務(wù)名稱")
self.add_button = QPushButton("添加")
self.remove_button = QPushButton("刪除選中")
self.remove_button.setEnabled(False)
self.todo_list = QListView()
self.todo_list.setModel(self.view_model.list_model)
self.stats_label = QLabel()
self.message_label = QLabel("請(qǐng)輸入任務(wù)名稱,然后點(diǎn)擊添加")
self._build_layout()
self._bind_events()
self._bind_view_model()
def _build_layout(self) -> None:
input_layout = QHBoxLayout()
input_layout.addWidget(self.task_input)
input_layout.addWidget(self.add_button)
button_layout = QHBoxLayout()
button_layout.addStretch()
button_layout.addWidget(self.remove_button)
root_layout = QVBoxLayout()
root_layout.addLayout(input_layout)
root_layout.addWidget(self.todo_list)
root_layout.addLayout(button_layout)
root_layout.addWidget(self.stats_label)
root_layout.addWidget(self.message_label)
container = QWidget()
container.setLayout(root_layout)
self.setCentralWidget(container)
def _bind_events(self) -> None:
self.add_button.clicked.connect(self._on_add_clicked)
self.task_input.returnPressed.connect(self._on_add_clicked)
self.remove_button.clicked.connect(self._on_remove_clicked)
self.todo_list.doubleClicked.connect(self._on_task_double_clicked)
self.todo_list.selectionModel().currentChanged.connect(self._on_selection_changed)
def _bind_view_model(self) -> None:
self.view_model.message_changed.connect(self.message_label.setText)
self.view_model.stats_changed.connect(self.stats_label.setText)
def _on_add_clicked(self) -> None:
ok = self.view_model.add_task(self.task_input.text())
if ok:
self.task_input.clear()
self.task_input.setFocus()
def _on_remove_clicked(self) -> None:
self.view_model.remove_task(self.todo_list.currentIndex().row())
def _on_task_double_clicked(self, index: QModelIndex) -> None:
self.view_model.toggle_task(index.row())
def _on_selection_changed(self, current: QModelIndex) -> None:
self.remove_button.setEnabled(current.isValid())
def main() -> int:
app = QApplication(sys.argv)
view_model = TodoListViewModel()
window = MainWindow(view_model)
window.show()
return app.exec()
if __name__ == "__main__":
raise SystemExit(main())
運(yùn)行:
python app.py
十一、這個(gè)例子里的 MVVM 關(guān)系
可以用一張簡(jiǎn)單的關(guān)系圖理解:
用戶點(diǎn)擊按鈕
|
v
View(MainWindow)
|
v
ViewModel(add_task / remove_task / toggle_task)
|
v
Model(TodoItem)
|
v
ViewModel 發(fā)出 message_changed / stats_changed 信號(hào)
|
v
View 更新 QLabel / QListView
關(guān)鍵點(diǎn)是:View 不直接處理業(yè)務(wù)。
比如添加任務(wù)時(shí),View 只做:
ok = self.view_model.add_task(self.task_input.text())
至于:
- 標(biāo)題能不能為空。
- 添加到哪里。
- 統(tǒng)計(jì)信息怎么更新。
- 錯(cuò)誤信息怎么提示。
這些都放在 ViewModel 中。
十二、為什么不用 MainWindow 直接寫完
當(dāng)然可以直接寫在 MainWindow 里:
def on_add_clicked(self):
title = self.task_input.text().strip()
if not title:
self.message_label.setText("不能為空")
return
self.items.append(title)
self.list_widget.addItem(title)
小工具沒問題。
但項(xiàng)目一旦變復(fù)雜,就會(huì)出現(xiàn)這些問題:
- 窗口類越來越長。
- 業(yè)務(wù)邏輯和界面邏輯混在一起。
- 很難寫單元測(cè)試。
- 多個(gè)窗口復(fù)用同一套邏輯很麻煩。
- UI 改版時(shí)容易誤傷業(yè)務(wù)邏輯。
MVVM 的價(jià)值不是讓代碼變少,而是讓代碼邊界更清楚。
十三、初學(xué)者常見誤區(qū)
誤區(qū) 1:ViewModel 里不能出現(xiàn)任何 Qt 類型
在 PyQt 項(xiàng)目里,ViewModel 繼承 QObject,使用 pyqtSignal 是很常見的做法。它確實(shí)依賴 Qt,但它不應(yīng)該依賴具體窗口控件。
可以接受:
class UserViewModel(QObject):
message_changed = pyqtSignal(str)
不建議:
class UserViewModel(QObject):
def __init__(self, label: QLabel):
self.label = label
前者是用 Qt 的通信機(jī)制,后者是直接操控界面控件。
誤區(qū) 2:用了 QStandardItemModel 就等于用了 MVVM
不是。
QStandardItemModel 是 Qt 的界面數(shù)據(jù)模型,主要服務(wù)于 QListView、QTableView、QTreeView。
MVVM 的重點(diǎn)是:
- 業(yè)務(wù)數(shù)據(jù)放在哪里。
- 用戶命令放在哪里。
- View 和業(yè)務(wù)邏輯是否解耦。
- 狀態(tài)變化如何通知界面。
誤區(qū) 3:所有邏輯都要拆得很細(xì)
也不是。
如果只是一個(gè) 200 行的小工具,沒必要強(qiáng)行拆 10 個(gè)文件??梢韵扔脝挝募姹緦懬宄?/p>
- Model 類。
- ViewModel 類。
- View 類。
等功能變多,再拆目錄。
十四、繼續(xù)擴(kuò)展這個(gè) demo
這個(gè) demo 可以繼續(xù)擴(kuò)展成更像真實(shí)項(xiàng)目的結(jié)構(gòu):
- 加入文件保存:把任務(wù)列表保存為 JSON。
- 加入數(shù)據(jù)庫:用 SQLite 保存任務(wù)。
- 加入搜索框:View 調(diào)用 ViewModel 的過濾方法。
- 加入任務(wù)優(yōu)先級(jí):Model 增加
priority字段。 - 加入多窗口:多個(gè) View 共用同一個(gè) ViewModel 或共享 Service。
- 加入單元測(cè)試:直接測(cè)試 ViewModel 的添加、刪除、統(tǒng)計(jì)邏輯。
例如,保存 JSON 時(shí)可以加一個(gè) services/todo_repository.py:
import json
from pathlib import Path
from models.todo_item import TodoItem
class TodoRepository:
def __init__(self, path: str = "todos.json"):
self.path = Path(path)
def load(self) -> list[TodoItem]:
if not self.path.exists():
return []
data = json.loads(self.path.read_text(encoding="utf-8"))
return [TodoItem(**item) for item in data]
def save(self, items: list[TodoItem]) -> None:
data = [item.__dict__ for item in items]
self.path.write_text(
json.dumps(data, ensure_ascii=False, indent=2),
encoding="utf-8",
)
然后 ViewModel 依賴 Repository,而不是在 View 里讀寫文件。
十五、總結(jié)
使用 PyQt 開發(fā)桌面應(yīng)用時(shí),最容易犯的錯(cuò)誤是把所有代碼都塞進(jìn)窗口類。短期看寫得快,長期看維護(hù)成本會(huì)越來越高。
MVVM 的核心價(jià)值是分層:
- Model:保存業(yè)務(wù)數(shù)據(jù)。
- View:顯示界面,接收用戶操作。
- ViewModel:處理界面命令、業(yè)務(wù)狀態(tài)和信號(hào)通知。
在 PyQt 中,可以使用:
QObjectpyqtSignalQStandardItemModelQListViewQMainWindow
來搭建一套簡(jiǎn)單但清晰的 MVVM 架構(gòu)。
對(duì)于 Python 開發(fā)者來說,剛開始不需要追求完美架構(gòu)。建議先記住一句話:
讓窗口類少寫業(yè)務(wù)邏輯,讓 ViewModel 管狀態(tài)和命令。
只要做到這一點(diǎn),你的 PyQt 項(xiàng)目就會(huì)比“所有代碼都寫在 MainWindow 里”更容易維護(hù)。
以上就是基于Python+PyQt+MVVM方式開發(fā)桌面應(yīng)用的詳細(xì)內(nèi)容,更多關(guān)于Python PyQt MVVM桌面應(yīng)用的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
使用python將csv數(shù)據(jù)導(dǎo)入mysql數(shù)據(jù)庫
這篇文章主要為大家詳細(xì)介紹了如何使用python將csv數(shù)據(jù)導(dǎo)入mysql數(shù)據(jù)庫,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-05-05
Python Pandas list列表數(shù)據(jù)列拆分成多行的方法實(shí)現(xiàn)
這篇文章主要介紹了Python Pandas list(列表)數(shù)據(jù)列拆分成多行的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-12-12
python使用opencv實(shí)現(xiàn)馬賽克效果示例
這篇文章主要介紹了python使用opencv實(shí)現(xiàn)馬賽克效果,結(jié)合實(shí)例形式分析了Python使用cv2模塊操作圖片實(shí)現(xiàn)馬賽克效果的相關(guān)技巧,需要的朋友可以參考下2019-09-09
TensorFlow-GPU完美安裝與配置的實(shí)現(xiàn)步驟
本文主要介紹了TensorFlow-GPU的兩種安裝方法,推薦通過清華鏡像下載whl文件進(jìn)行高效安裝,并提供版本升級(jí)、路徑查詢和環(huán)境配置步驟,感興趣的可以了解一下2026-04-04
python?pdfplumber庫批量提取pdf表格數(shù)據(jù)轉(zhuǎn)換為excel
這篇文章主要為大家介紹了python使用pdfplumber庫批量提取pdf表格數(shù)據(jù)轉(zhuǎn)換為excel格式的示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06
Python利用神經(jīng)網(wǎng)絡(luò)解決非線性回歸問題實(shí)例詳解
這篇文章主要介紹了Python利用神經(jīng)網(wǎng)絡(luò)解決非線性回歸問題,結(jié)合實(shí)例形式詳細(xì)分析了Python使用神經(jīng)網(wǎng)絡(luò)解決非線性回歸問題的相關(guān)原理與實(shí)現(xiàn)技巧,需要的朋友可以參考下2019-07-07
Django模板導(dǎo)入母版繼承和自定義返回Html片段過程解析
這篇文章主要介紹了Django模板導(dǎo)入母版繼承和自定義返回Html片段過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-09-09

