PyCharm / PyQt:如何使用动态加载的ui文件获取代码完成

时间:2017-06-24 17:47:49

标签: python pyqt pycharm

假设我在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存根,例如:

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文件制作一些脚本来为我编写所有这些行。我只是想知道阅读我的代码的人是否会发现这种方法非常不正统,即使我清楚地评论为什么我要写一个技术无用的私人函数。

1 个答案:

答案 0 :(得分: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 Designer中遵循的命名约定。通过执行此操作,将忽略由Qt Designer自动生成的名称的小部件(如果我关心这些,我会给它们一个正确的名称)。对于任何其他命名约定或其他类型的对象(例如操作),更新它应该是直截了当的。

为方便起见,我已将其设置为PyCharm中的外部工具;请参见屏幕截图here(根据需要更改路径)。这样,我只需要在项目窗口中右键单击我的ui文件,然后外部工具 - &gt;用于Qt UI文件的存根生成器,我在运行窗口中得到以下输出,可以复制:

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