PyCharm/PyQt:如何通过动态加载的 ui 文件获得代码补全 [英] PyCharm / PyQt: How to obtain code completion with dynamically loaded ui files

查看:77
本文介绍了PyCharm/PyQt:如何通过动态加载的 ui 文件获得代码补全的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我在 Qt Designer 中创建了一个 ui 文件,我想动态加载该文件以操作小部件,例如:

Let's say I have a ui file created in Qt Designer that I want to load dynamically to then manipulate the widgets, such as:

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文件加载后写在构造函数中):

At the moment I am using this (written in the constructor after the ui file is loaded):

self.myPushButton = self.myPushButton  # type: QtWidgets.QPushButton
# Code completion for myPushButton works at this point

我也想过这个,但是好像不行:

I also thought of this, but it does not seem to do the trick:

assert isinstance(self.myPushButton, QtWidgets.QPushButton)
# PyCharm does not even recognise myPushButton as an attribute of self at this point

最后我也想到了用python stubs,比如:

Finally, I also thought of using python stubs, such as:

example.pyi:

class MyWidget(QtWidgets.QWidget):

    def __init__(self):
        self.myPushButton: QtWidgets.QPushButton = ... 

但是,myPushButton 在 example.py 外部的代码中被正确识别,但在 example.py 本身的代码中却没有被正确识别,这与我想要的相反.

However, myPushButton is properly recognised in code outside example.py but not in code inside example.py itself, which is kind of the opposite of what I wanted.

我也在考虑采用我的第一种方法,但所有这些行都放在一个永远不会被调用的私有方法中,例如:

I am also considering taking my first approach but with all those lines put in a private method that will never get called, such as:

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

This seems to work fine, and it also allows me to group all my type hinting code together, isolated from the rest. I could even make some script to write all those lines for me by parsing the ui files. I am just wondering if people reading my code would find this approach very unorthodox, even if I comment clearly why am I writing a technically useless private function.

推荐答案

如果有人感兴趣,我制作了我提到的脚本来解析 .ui 文件并生成准备复制到我的班级的存根代码:

If anybody is interested, I made the script I mentioned to parse the .ui files and generate stub code ready to be copied to my class:

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 设计器自动生成名称的小部件将被忽略(如果我关心这些,我会给它们一个正确的名称).为任何其他命名约定或其他类型的对象(例如操作)更新它应该很简单.

This only parses widgets whose names start with 'ui' followed by an uppercase letter, such as 'uiMyWidget', which is the naming convention that I typically follow in the Qt Designer. By doing this, the widgets with names automatically generated by the Qt Designer are ignored (if I cared about these, I would have given them a proper name). It should be straightforward to update this for any other naming conventions, or other type of objects, such as actions.

为方便起见,我也将其设置为 PyCharm 中的外部工具;查看屏幕截图此处(根据需要更改路径).这样,我只需在项目窗口中右键单击我的 ui 文件,然后 External Tools -> Stub Generator for Qt UI Files,然后在运行窗口中就可以得到以下输出复制:

For convenience, I have set this up as an external tool in PyCharm as well; see screenshot here (change the paths as appropriate). That way, I only have to right-click my ui file in the project window, then External Tools -> Stub Generator for Qt UI Files, and I get the following output in the Run window ready to be copied:

C:ProgramDataAnaconda3python.exe D:MyProjectinui_stub_generator.py D:MyProjectmy_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

这篇关于PyCharm/PyQt:如何通过动态加载的 ui 文件获得代码补全的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆