PyCharm / PyQt:如何通过动态加载的 ui 文件获得代码补全
Posted
技术标签:
【中文标题】PyCharm / PyQt:如何通过动态加载的 ui 文件获得代码补全【英文标题】:PyCharm / PyQt: How to obtain code completion with dynamically loaded ui files 【发布时间】:2022-01-21 01:52:18 【问题描述】:假设我在 Qt Designer 中创建了一个 ui 文件,我想动态加载该文件以操作小部件,例如:
example.py:
from PyQt5 import QtWidgets, uic
class MyWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(MyWidget, self).__init__(parent)
uic.loadUi('example.ui', self)
# No code completion here for self.myPushButton:
self.myPushButton.clicked.connect(self.handleButtonClick)
self.show()
在 PyCharm (2017.1.4) 中以这种方式加载的小部件是否有标准/便捷的方式来启用代码完成?
目前我正在使用这个(在ui文件加载后写在构造函数中):
self.myPushButton = self.myPushButton # type: QtWidgets.QPushButton
# Code completion for myPushButton works at this point
我也想过这个,但是好像不行:
assert isinstance(self.myPushButton, QtWidgets.QPushButton)
# PyCharm does not even recognise myPushButton as an attribute of self at this point
最后我也想到了用python stubs,比如:
example.pyi:
class MyWidget(QtWidgets.QWidget):
def __init__(self):
self.myPushButton: QtWidgets.QPushButton = ...
但是,myPushButton 在 example.py 外部的代码中被正确识别,但在 example.py 本身的代码中却没有被正确识别,这与我想要的相反。
我也在考虑采用我的第一种方法,但所有这些行都放在一个永远不会被调用的私有方法中,例如:
example.py:
from PyQt5 import QtWidgets, uic
class MyWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(MyWidget, self).__init__(parent)
uic.loadUi('example.ui', self)
# Code completion now works here for self.myPushButton:
self.myPushButton.clicked.connect(self.handleButtonClick)
self.show()
def __my_private_method_never_called():
self.myPushButton = self.myPushButton # type: QtWidgets.QPushButton
# Or even this (it should have the same effect if this
# function is never called, plus it is less verbose):
self.myPushButton = QtWidgets.QPushButton()
# If I want to make sure that this is never called
# could raise an error at some point:
raise YouShouldNotHaveCalledThisError()
这似乎工作正常,它还允许我将所有类型提示代码组合在一起,与其他代码隔离。我什至可以通过解析 ui 文件来编写一些脚本来为我编写所有这些行。我只是想知道阅读我的代码的人是否会觉得这种方法非常不正统,即使我清楚地评论了为什么我要编写一个技术上无用的私有函数。
【问题讨论】:
Pycharm 无法识别 .ui 文件 【参考方案1】:如果有人感兴趣,我制作了我提到的脚本来解析 .ui 文件并生成准备复制到我的班级的存根代码:
ui_stub_generator.py:
from __future__ import print_function
import os
import sys
import xml.etree.ElementTree
def generate_stubs(file):
root = xml.etree.ElementTree.parse(file).getroot()
print('Stub for file: ' + os.path.basename(file))
print()
print(' def __stubs(self):')
print(' """ This just enables code completion. It should never be called """')
for widget in root.findall('.//widget'):
name = widget.get('name')
if len(name) > 3 and name[:2] == 'ui' and name[2].isupper():
cls = widget.get('class')
print(' self. = QtWidgets.()'.format(
name, cls
))
print(' raise AssertionError("This should never be called")')
print()
def main():
for file in sys.argv[1:]:
generate_stubs(file)
if __name__ == '__main__':
main()
这仅解析名称以“ui”开头后跟大写字母的小部件,例如“uiMyWidget”,这是我在 Qt 设计器中通常遵循的命名约定。通过这样做,Qt 设计器自动生成名称的小部件将被忽略(如果我关心这些,我会给它们一个正确的名称)。为任何其他命名约定或其他类型的对象(例如操作)更新它应该很简单。
为方便起见,我也将其设置为 PyCharm 中的外部工具;请参阅屏幕截图here(根据需要更改路径)。这样,我只需要在项目窗口中右键单击我的 ui 文件,然后 External Tools -> Stub Generator for Qt UI Files,然后在运行窗口中就可以得到以下输出复制:
C:\ProgramData\Anaconda3\python.exe D:\MyProject\bin\ui_stub_generator.py D:\MyProject\my_ui_file.ui
Stub for file: my_ui_file.ui
def __stubs(self):
""" This just enables code completion. It should never be called """
self.uiNameLabel = QtWidgets.QLabel()
self.uiOpenButton = QtWidgets.QPushButton()
self.uiSplitter = QtWidgets.QSplitter()
self.uiMyCombo = QtWidgets.QComboBox()
self.uiDeleteButton = QtWidgets.QPushButton()
raise AssertionError("This should never be called")
Process finished with exit code 0
【讨论】:
这是个好主意。我遇到了自动完成无法识别的 ui 文件的相同问题。我没有想到。也欢迎您将脚本包含在外部工具中。我把它当作我自己的用途。谢谢。 @StephanePiriou 只是为了给你更多的想法:除了在控制台中显示__stubs
方法之外,我的脚本的最终版本会检查具有相同名称的 Python 文件/类(例如 @ 987654325@ / MyUiFile
) 并直接替换该方法。然后,我使用.ui
文件观察器自动执行脚本。我在工作中这样做了,所以我不能在这里展示它,但我会在某个时候用一个简化的版本来编辑我的答案
@FenAndr 那么存根代码将被添加到哪里?在单独的 .pyi 文件中? PyCharm 似乎没有索引它..
@maicol07 在普通的.py
文件中,在类本身的某个地方。除非最新的 PyCharm 版本在这方面发生了巨大的变化,否则这个解决方案应该仍然有效。 AFAIK,.pyi
文件仅用于访问您的类的外部代码,而不是在类本身内,因此不能用于此。
@FernAndr 我实际上正在尝试 PySide2,所以我现在正在编写没有类的代码,只是主要的。我应该将 self 变量更改为我的窗口变量吗?以上是关于PyCharm / PyQt:如何通过动态加载的 ui 文件获得代码补全的主要内容,如果未能解决你的问题,请参考以下文章