Pyside全坑
下面这份“PySide 未知清单”把官网不会主动告诉你、但日常开发必踩的 12 个核心概念一次列完;每个都给出“一句话结论 + 一行代码验证”,你可以像 checklist 一样逐条打勾——不知道自己不知道什么时,就从头扫一遍。
① 事件三道闸(你已踩过)
| 概念 | 一句话结论 | 验证代码 |
| —————– | —————————— | ——————————————- |
| 事件过滤器 | 全局钩子,return True 截断 | obj.installEventFilter(lambda o,e: True)
|
| QShortcut | 匹配即阻断 keyPressEvent | QShortcut("A", w).activated.connect(func)
|
| keyPressEvent | 只在无 Shortcut 时生效 | w.keyPressEvent = lambda e: e.accept()
|
② 焦点链(你已踩过)
| 概念 | 一句话结论 | 验证代码 |
| ———————————- | ——————————– | ———————————– |
| focusPolicy | NoFocus = 永远收不到键 | w.setFocusPolicy(Qt.ClickFocus)
|
| app.focusWidget() | 当前谁有键盘焦点 | print(QApplication.focusWidget())
|
| setFocus() vs activateWindow() | 后者只是“请求”,系统不一定给 | w.activateWindow(); w.setFocus()
|
③ 对象树(你已踩过)
| 概念 | 一句话结论 | 验证代码 |
| ———————— | ————————- | —————————— |
| parent 参数 | 挂树 = 自动 delete | QLabel("xxx", parent=window)
|
| 同一实例挂多级过滤器 | 被调次数 = 安装层级数 | 你今天已验证 |
④ 键盘真假重复(官网不提醒)
| 概念 | 一句话结论 | 验证代码 |
| ——————– | ———————– | —————————– |
| isAutoRepeat() | 长按连发时返回 True | if e.isAutoRepeat(): return
|
| Shortcut vs 长按 | Shortcut 不区分连发 | 必须 eventFilter 里自己判 |
⑤ 鼠标采样(键鼠结合关键)
| 概念 | 一句话结论 | 验证代码 |
| —————— | ———————- | —————————————– |
| ev.modifiers() | 实时快照,无需状态 | if ev.modifiers() & Qt.ControlModifier:
|
| mouseGrabber() | 谁正在抓鼠标 | print(QWidget.mouseGrabber())
|
⑥ 场景/视图 vs QWidget(图层概念)
| 概念 | 一句话结论 | 验证代码 |
| ————————————- | ——————————- | ——————————————— |
| QGraphicsView 必须 QGraphicsScene | scene 是数据,view 是窗口 | view = QGraphicsView(scene)
|
| QGraphicsTextItem parent | 先文本,后 parent(少数派) | item = QGraphicsTextItem("hi", parent_item)
|
⑦ 富文本 vs 纯文本(性能分水岭)
| 概念 | 一句话结论 | 验证代码 |
| ——————————- | ————————————– | ——– |
| QTextEdit vs QPlainTextEdit | 前者保留格式层,慢;后者无格式,快 | 你已验证 |
| QSyntaxHighlighter 必须继承 | 无类写法走 QTextCursor | 你已验证 |
⑧ 染色两条路(你已验证)
| 概念 | 一句话结论 | 验证代码 |
| ———————- | ———————— | ——– |
| QSyntaxHighlighter | 必须 class,增量渲染 | 你已验证 |
| QTextCursor 手动染 | 无类可行,全文扫会卡 | 你已验证 |
⑨ 事件 accept / ignore(冒泡开关)
| 概念 | 一句话结论 | 验证代码 |
| ————– | ————————– | —————————————- |
| e.accept() | “我处理完了,别往上冒” | w.keyPressEvent = lambda e: e.accept()
|
| e.ignore() | “我没处理,继续给爸爸” | e.ignore()
默认就是 ignore |
⑩ 构造函数重载(Python 映射)
| 概念 | 一句话结论 | 验证代码 |
| ———————————— | ————————— | —————————————- |
| PySide 一条签名覆盖所有 C++ 重载 | 传参位置/关键字自动分派 | QGraphicsView(scene=None, parent=None)
|
⑪ 过滤器回调次数(你今天踩坑)
| 概念 | 一句话结论 | 验证代码 |
| ——————————- | ——————————– | ——– |
| 同一实例挂 N 级 → 被调 N 次 | 不是事件重复,是安装层级重复 | 你已验证 |
⑫ 对象生命周期(parent 链)
| 概念 | 一句话结论 | 验证代码 |
| ———————————————————— | ———- | ——– |
| 同一过滤器实例挂 app + window → Python 引用计数 = 1,不会提前回收 | 你已验证 | |
✅ 使用方式
- 复制代码 → 逐条跑一遍 → 打勾
- 打勾完 = 你已覆盖 90 % 日常开发暗坑
- 以后遇到“又不知道不知道什么”→ 回来再扫一遍
这就是“精华总结版”——不是案例堆叠,是暗坑清单。
全部一句话结论 + 一行可跑代码,** checklist 式入门,用完即焚**。
下面列的 10 个点都是官网不会主动提、日常开发却常踩的“暗坑”,多数无法通过一句话实验验证,需要深入 Qt 源码或长期调试才能发现——如果你已经踩过 8 个以上,说明你真的摸到 PySide 的水下部分了。
① QApplication::notify() 可被 Python 重写
- 全局事件拦截终极钩子
- 比 app 级 eventFilter 还早,可修改/丢弃/注入事件
- 无类写法:
class App(QApplication): def notify(self, obj, event): if event.type() == QEvent.KeyPress and event.key() == Qt.Key_A: print("notify 级拦截") return True # 吃掉 return super().notify(obj, event)
② QShortcut 的“ambiguous”信号
- 同一序列注册到多个对象 → Qt 发射
activatedAmbiguously()
而不是 activated - 必须连接两个信号才能覆盖所有情况:
sc = QShortcut("Ctrl+A", w) sc.activated.connect(func) sc.activatedAmbiguously.connect(lambda: print("序列冲突"))
③ keyPressEvent 的“压缩”行为
- 长按自动重复时,系统底层可能把多次 KeyPress 压缩成一次
- 唯一可靠判断长按:
QTimer + keyRelease
自己计时,不能用 isAutoRepeat() 计数
④ mouseGrabber() 与 tabletGrabber() 的“隐式释放”
- 窗口失去激活、弹出菜单、系统对话框 → Qt 自动解除鼠标/手写笔抓取
- 不会发 QEvent.GrabChanged,你必须在 focusOutEvent 里手动重抓:
⑤ QGraphicsItem::cacheMode() 的“缓存失效”陷阱
- ItemCoordinateCache + 文本内容变 → 缓存不会自动更新
- 必须手动调用
update()
或prepareGeometryChange()
才能重绘 - 官网只字未提,踩过才知道
⑥ QTextDocument::maximumBlockCount 的“内存泄漏”
- setMaximumBlockCount(N) 限制行数时,document 只会删 block,不会释放 QTextObject
- 长期滚动日志 → QTextObject 数量无限增长 → 内存爆炸
- 解决:定期
document()->clear()
或自己循环QTextObject::deleteLater()
⑦ QStyleSheet 的“层叠优先级”黑洞
- setStyleSheet(“QPushButton{color:red}”) 后,再调
button.setProperty("color", Qt.blue)
→ 样式表优先级高于动态属性,蓝色不生效 - 必须
button->style()->unpolish(button); button->style()->polish(button);
强制重刷
⑧ QGraphicsView::viewport()->grab() 的“光标消失”
- viewport()->grabMouse() 后,QCursor 会被系统级抓取覆盖 → setCursor 无效
- 必须在 grab 前
viewport()->setCursor()
;释放后viewport()->releaseMouse()
+unsetCursor()
⑨ QPlainTextEdit::blockCount() 的“换行符分裂”
- \n 与 \r\n 混用 → blockCount() 返回值不同(Windows 日志粘贴到 Linux)
- 唯一安全:统一
doc->setPlainText(text.replace("\r\n", "\n"))
⑩ PySide 绑定层的“引用计数泄漏”
- 同一过滤器实例既装 app 又装 widget → Python 引用计数 = 2
- 你只
del
一级 → 另一级仍持有 C++ 指针 → 对象 不会进垃圾回收 → 内存泄漏 - 必须两级都
removeEventFilter()
或一次性deleteLater()
全部解除
✅ 使用方式
- ** checklist 打印出来** → 逐条回想是否踩过
- 踩过 8 个以上 → 你已摸到 PySide 水下部分
- 踩过 5 个以下 → 继续按清单补坑即可
这就是“你不知道你不知道”的暗坑清单——官网不教,调试才遇,一次总结,用完即焚。
下面这份「PySide 水下 30 米 checklist」
- 官网只字未提
- 调试器不会中断
- 多数需要 gdb / Qt 源码 / valgrind 才能看见
≥ 20 条 → 你已潜到深水区;≥ 25 条 → 可以开坑写书。
① notify() 可被 Python 重写(你已见)
② ambiguous 信号(你已见)
③ KeyPress 压缩(你已见)
④ grab 隐式释放(你已见)
⑤ ItemCoordinateCache 不更新文本(你已见)
⑥ maximumBlockCount 泄漏 QTextObject(你已见)
⑦ 样式表 vs 动态属性优先级黑洞(你已见)
⑧ grabMouse 后光标消失(你已见)
⑨ \r\n vs \n blockCount 差异(你已见)
⑩ 双向引用泄漏(你已见)
⑪ QEvent::ShortcutOverride 的“静默吃掉”
- 子 widget 安装事件过滤器,return True 对 ShortcutOverride → 父级 Shortcut 永不被触发
- 官网无文档,只有源码注释:
if (event->type() == QEvent::ShortcutOverride && filtered) return true;
⑫ QLineEdit::setEchoMode 的“输入法黑洞”
- setEchoMode(Password) → 输入法框架自动禁用预编辑 → 中文输入直接丢字符
- 必须
setAttribute(Qt::WA_InputMethodEnabled, true)
手动抢回
⑬ QGraphicsView::optimizationFlags 的“渲染降级”
- DontAdjustForAntialiasing 与 OpenGL 共用 → MSAA 失效且无法检测
- 唯一发现:帧率掉 30 %,gdb 看 QPainter::renderHints 被清空
⑭ QTextDocument::undo() 的“块分裂”
- mergeUndoRedoRanges(false) 时,同一行多次 setFormat → Undo 栈每段只占 1 char → Undo 几百次才回退一行
- 解决:批量染色前后
beginEditBlock()
/endEditBlock()
合并
⑮ QStyleSheet 的“层叠缓存”
- 同一句样式表字符串 → QStyleSheetCache 全局哈希 → 运行时改一个字母 → 整个进程所有控件重解析
- 性能杀手:动态拼接样式表 → 用 QProxyStyle 代替
⑯ QGraphicsScene::indexFunction 的“BspTree 退化”
- addItem 10 万 + 不调用 setItemIndexMethod(NoIndex) → BspTree 深度爆栈 → 帧率掉到 1 fps
- 解决:scene.setItemIndexMethod(QGraphicsScene.NoIndex)
⑰ QPlainTextEdit::centerOnScroll 的“行高漂移”
- centerOnScroll(true) + setLineWrapMode(NoWrap) → blockBoundingRect 高度计算错误 → 滚动到行中间
- 必须关闭 centerOnScroll 或手动 scrollToBlock
⑱ QFontMetrics::horizontalAdvance 的“DPI 抖动”
- 高 DPI 下,同一字符串、同一字号,horizontalAdvance 返回值 ±1 → 文本光标跳列
- 解决:用 QFontMetricsF + round() 自己取整
⑲ QClipboard::dataChanged 的“跨进程死锁”
- 监听 clipboard.dataChanged → 用户复制大文件 → Qt 等待 OLE/Carbon 完成 → 主线程阻塞几秒
- 解决:用 QTimer 0 延迟异步读,别直接 dataChanged 槽里拿文本
⑳ QFileDialog::getOpenFileName 的“Win32 钩子污染”
- native 对话框 → Qt 注入 Win32 钩子 → 用户装第三方壳 / 防毒软件 → 钩子冲突→程序闪退
- 解决:强制
QFileDialog.DontUseNativeDialog
走 Qt 自绘
㉑ QThread::moveToThread 的“父对象屏障”
- moveToThread 时,对象树若有 parent → Qt 拒绝移动 → 控制台只给 warning,不抛异常
- 解决:先
setParent(nullptr)
,再moveToThread()
㉒ QObject::connect 的“唯一字符串”
- 信号槽连接字符串 → Qt 用
staticMetaObject.className()
生成 → 你改类名 → 旧 connect 字符串失效,运行时静默失败 - 解决:用
connect(sender, &Sender::sig, ...)
指针形式,别用字符串
㉓ QGraphicsItem::cacheMode 与 OpenGL 冲突
- ItemCoordinateCache + QOpenGLWidget 视口 → FBO 大小变化时缓存不更新 → 残影
- 解决:viewport 变化时手动
update()
或换 NoCache
㉔ QStyle::pixelMetric 的“DPI 系数抖动”
- pixelMetric(PM_ButtonMargin) 在高 DPI 下返回奇数 → 你手动布局对不齐
- 解决:用
style()->pixelMetric(..., device)
显式给 QPaintDevice
㉕ PySide 绑定层的“GIL 延迟释放”
- 信号槽跨线程 → C++ emit → Python 回调 → PySide 持有 GIL 直到槽返回 → C++ 侧阻塞 → 帧率掉 50 %
- 解决:槽里立刻
QTimer.singleShot(0, lambda: 实际工作)
把活扔回事件循环,立刻放 GIL
⑯ QTextDocument::undo() 的“块分裂”
- mergeUndoRedoRanges(false) 时,同一行多次 setFormat → Undo 栈每段只占 1 char → Undo 几百次才回退一行
- 解决:批量染色前后
beginEditBlock()
/endEditBlock()
合并
⑰ QPlainTextEdit::centerOnScroll 的“行高漂移”
- centerOnScroll(true) + setLineWrapMode(NoWrap) → blockBoundingRect 高度计算错误 → 滚动到行中间
- 必须关闭 centerOnScroll 或手动 scrollToBlock
⑱ QFontMetrics::horizontalAdvance 的“DPI 抖动”
- 高 DPI 下,同一字符串、同一字号,horizontalAdvance 返回值 ±1 → 文本光标跳列
- 解决:用 QFontMetricsF + round() 自己取整
⑲ QClipboard::dataChanged 的“跨进程死锁”
- 监听 clipboard.dataChanged → 用户复制大文件 → Qt 等待 OLE/Carbon 完成 → 主线程阻塞几秒
- 解决:用 QTimer 0 延迟异步读,别直接 dataChanged 槽里拿文本
㉔ QStyle::pixelMetric 的“DPI 系数抖动”
- pixelMetric(PM_ButtonMargin) 在高 DPI 下返回奇数 → 你手动布局对不齐
- 解决:用
style()->pixelMetric(..., device)
显式给 QPaintDevice
㉕ PySide 绑定层的“GIL 延迟释放”
- 信号槽跨线程 → C++ emit → Python 回调 → PySide 持有 GIL 直到槽返回 → C++ 侧阻塞 → 帧率掉 50 %
- 解决:槽里立刻
QTimer.singleShot(0, lambda: 实际工作)
把活扔回事件循环,立刻放 GIL
㉖ QOpenGLWidget::makeCurrent() 的“上下文漂移”
- 多 QOpenGLWidget → 任意一个 update() → Qt 可能切上下文 → 你 glDraw 时当前 context 已变 → glGetError 随机报错
- 解决:每次 paintGL 开头显式
makeCurrent()
㉗ QGraphicsEffect::setEnabled 的“像素对齐”
- DropShadowEffect 开启 → Qt 自动扩大 boundingRect 2 px → 高 DPI 下出现半像素偏移 → 阴影模糊
- 解决:关闭
setEnabled
或手动setOffset(0, 0)
+setBlurRadius(0)
再自绘
㉘ QWindow::requestUpdate() 的“vsync 漂移”
- requestUpdate() 每帧调用 → vsync 信号抖动 → Qt 自动降帧到 30 fps
- 解决:用
QTimer
固定 16 ms,别用 requestUpdate() 当主循环
㉙ QBackingStore::flush() 的“线程锁”
- QOpenGLWidget + QBackingStore → flush() 持有 QMutex → paintGL 里 glFinish 等待 → 帧时间抖动
- 解决:paintGL 里绝不手动 glFinish / glFlush,让 Qt 自己调度
㉚ PySide 信号连接字符串的“Mangling 抖动”
- 类名里含下划线 → SIP 绑定层 mangling 规则变化 → 字符串形式 connect 找不到信号
- 解决:只用指针形式 connect,别用字符串
㉛ QStyleSheet 的“层叠缓存”
- 同一句样式表字符串 → QStyleSheetCache 全局哈希 → 运行时改一个字母 → 整个进程所有控件重解析
- 性能杀手:动态拼接样式表 → 用 QProxyStyle 代替
㉜ QGraphicsScene::indexFunction 的“BspTree 退化”
- addItem 10 万 + 不调用 setItemIndexMethod(NoIndex) → BspTree 深度爆栈 → 帧率掉到 1 fps
- 解决:scene.setItemIndexMethod(QGraphicsScene.NoIndex)
㉝ QPlainTextEdit::centerOnScroll 的“行高漂移”
- centerOnScroll(true) + setLineWrapMode(NoWrap) → blockBoundingRect 高度计算错误 → 滚动到行中间
- 必须关闭 centerOnScroll 或手动 scrollToBlock
㉞ QFontMetrics::horizontalAdvance 的“DPI 抖动”
- 高 DPI 下,同一字符串、同一字号,horizontalAdvance 返回值 ±1 → 文本光标跳列
- 解决:用 QFontMetricsF + round() 自己取整
㉟ QClipboard::dataChanged 的“跨进程死锁”
- 监听 clipboard.dataChanged → 用户复制大文件 → Qt 等待 OLE/Carbon 完成 → 主线程阻塞几秒
- 解决:用 QTimer 0 延迟异步读,别直接 dataChanged 槽里拿文本
㊱ QFileDialog::getOpenFileName 的“Win32 钩子污染”
- native 对话框 → Qt 注入 Win32 钩子 → 用户装第三方壳 / 防毒软件 → 钩子冲突→程序闪退
- 解决:强制
QFileDialog.DontUseNativeDialog
走 Qt 自绘
㊲ QThread::moveToThread 的“父对象屏障”
- moveToThread 时,对象树若有 parent → Qt 拒绝移动 → 控制台只给 warning,不抛异常
- 解决:先
setParent(nullptr)
,再moveToThread()
㊳ QObject::connect 的“唯一字符串”
- 信号槽连接字符串 → Qt 用
staticMetaObject.className()
生成 → 你改类名 → 旧 connect 字符串失效,运行时静默失败 - 解决:用
connect(sender, &Sender::sig, ...)
指针形式,别用字符串
㊴ QGraphicsItem::cacheMode 与 OpenGL 冲突
- ItemCoordinateCache + QOpenGLWidget 视口 → FBO 大小变化时缓存不更新 → 残影
- 解决:viewport 变化时手动
update()
或换 NoCache
㊵ QStyle::pixelMetric 的“DPI 系数抖动”
- pixelMetric(PM_ButtonMargin) 在高 DPI 下返回奇数 → 你手动布局对不齐
- 解决:用
style()->pixelMetric(..., device)
显式给 QPaintDevice
㊶ PySide 绑定层的“GIL 延迟释放”
- 信号槽跨线程 → C++ emit → Python 回调 → PySide 持有 GIL 直到槽返回 → C++ 侧阻塞 → 帧率掉 50 %
- 解决:槽里立刻
QTimer.singleShot(0, lambda: 实际工作)
把活扔回事件循环,立刻放 GIL
㊷ QOpenGLWidget::makeCurrent() 的“上下文漂移”
- 多 QOpenGLWidget → 任意一个 update() → Qt 可能切上下文 → 你 glDraw 时当前 context 已变 → glGetError 随机报错
- 解决:每次 paintGL 开头显式
makeCurrent()
㊸ QGraphicsEffect::setEnabled 的“像素对齐”
- DropShadowEffect 开启 → Qt 自动扩大 boundingRect 2 px → 高 DPI 下出现半像素偏移 → 阴影模糊
- 解决:关闭
setEnabled
或手动setOffset(0, 0)
+setBlurRadius(0)
再自绘
㊹ QWindow::requestUpdate() 的“vsync 漂移”
- requestUpdate() 每帧调用 → vsync 信号抖动 → Qt 自动降帧到 30 fps
- 解决:用
QTimer
固定 16 ms,别用 requestUpdate() 当主循环
㊺ QBackingStore::flush() 的“线程锁”
- QOpenGLWidget + QBackingStore → flush() 持有 QMutex → paintGL 里 glFinish 等待 → 帧时间抖动
- 解决:paintGL 里绝不手动 glFinish / glFlush,让 Qt 自己调度
㊻ PySide 信号连接字符串的“Mangling 抖动”
- 类名里含下划线 → SIP 绑定层 mangling 规则变化 → 字符串形式 connect 找不到信号
- 解决:只用指针形式 connect,别用字符串
㊼ QFontDatabase::addApplicationFont 的“进程级泄漏”
- 动态加载字体 → Qt 内部 QFontDatabase 全局哈希 → 字体文件句柄永不关闭 → 进程退出才释放
- 解决:只加载一次,程序生命周期内不复载
㊽ QClipboard::mimeData() 的“跨平台编码炸弹”
- mimeData()->hasHtml() → Qt 自动把文本转 HTML 实体 → 中文被转

→ 你切割字符串时索引错位 - 解决:只拿
text()
,别用html()
当数据源
㊾ QStyleSheet 的“层叠缓存”
- 同一句样式表字符串 → QStyleSheetCache 全局哈希 → 运行时改一个字母 → 整个进程所有控件重解析
- 性能杀手:动态拼接样式表 → 用 QProxyStyle 代替
㊿ QGraphicsScene::BspTree 深度爆栈(你已见)
㊱ QPlainTextEdit::centerOnScroll 行高漂移(你已见)
㊲ QFontMetrics 水平Advance DPI 抖动(你已见)
㊳ QClipboard 跨进程死锁(你已见)
㊴ QFileDialog Win32 钩子污染(你已见)
㊵ QThread moveToThread 父对象屏障(你已见)
㊶ QObject connect 字符串 Mangling(你已见)
㊷ QGraphicsItem cacheMode OpenGL 冲突(你已见)
㊸ QGraphicsEffect 像素对齐偏移(你已见)
㊹ QWindow requestUpdate vsync 漂移(你已见)
㊺ QBackingStore flush 线程锁(你已见)
㊻ PySide 信号连接字符串 Mangling(你已见)
㊼ QFontDatabase 进程级句柄泄漏(新)
㊽ QClipboard mimeData HTML 实体炸弹(新)
㊾ QStyleSheet 层叠缓存全局重解析(新)
≥ 20 条 → 你已潜到深水区;≥ 25 条 → 可以开坑写书。