3

I am working on an app using Python3 and PyQt5, with my UI layout begin defined in a Qt .ui file and loaded into my QDialog class at runtime. Therefore, the instances of my UI elements, such as a QPushButton, are automatically assigned as instance variables of my QDialog class when the UI is loaded in.

The problem with this is that when I go to use these variables to modify the elements, I don't get any kind of Intellisense type hinting or autocompletion because Python and my IDE have no clue what the object's class is.

In Java, you can use explicit narrowing casting cast your object to the correct type, at which point intellisense can begin providing autocompletion because it knows the type.

For example, in Java and Android SDK:

TextView name = (TextView) findViewById(R.id.name); 

In this example, findViewById returns a View, so you can use type casting to ensure that it is cast to a TextView.


In Python, I'd like to do the same kind of thing, both to ensure that the UI elements I'm accessing are the correct type and to be able to use autocompletion for instance methods.

Here is my current situation:

""" 
Because Python doesn't know whether self.my_push_button is an instance 
of QPushButton, this may or may not work, but it won't know until runtime, 
so no autocompletion.
"""
self.my_push_button.setEnabled(False) 

What I want to be able to do is something like this:

( (QPushButton) self.my_push_button ).setEnabled(False) 

I tried this:

QPushButton(self.my_push_button).setEnabled(False)

But from what I can tell, it duplicates the original object and performs setEnabled on the new object, which is obviously not what I want.

I also tried using assert statements with the isinstance function:

assert isinstance(self.my_push_button, QPushButton)

"""
This works for providing the code completion and checking the type.
However, it only works within the scope of the assert statement, so adding an assert for each variable in each scope in which it is used would be unnecessarily verbose.
"""
self.my_push_button.setEnabled(False) 

I understand that there's not real "casting" with objects in Python, but it there any way to be able to something similar in Python to narrowing casting, as shown above, in Java?

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
oaky_afterbirth
  • 125
  • 3
  • 15
  • 2
    This is a function **of your IDE**. In PyCharm you can use `assert` statements. – Martijn Pieters Nov 08 '19 at 16:43
  • 2
    We'll need to know what IDE you are actually using. – Martijn Pieters Nov 08 '19 at 16:44
  • I am using PyCharm. I did try using assert statements, and it did work as I wanted it to, but only within the scope of the assert statement. I have about 25 different variables that I need to perform these asserts on, so it would make the code quite verbose to add these asserts to every single function in which the variables are used, if that makes any sense. – oaky_afterbirth Nov 08 '19 at 16:47
  • 1
    +1 for what Martijn said but also you can type hint in python. Also you are right with the "it duplicates the original object and performs". If you want to check types you can do something like `isinstance(self.my_push_button, QPushButton)` but I wouldnt recommend this. Python is a strong typed language, let it handle the types, dont try to force it. – Error - Syntactical Remorse Nov 08 '19 at 16:47
  • 1
    @AndrewFink I would not try to do what you are doing. Dont try to turn python into a weak typed language like Java or C#, python is much more powerful when you let it remain dynamic. Your IDE should type hint just fine. Pylint also does a great job at this. – Error - Syntactical Remorse Nov 08 '19 at 16:49
  • 1
    I know no IDE can detect dynamically generated attributes that is what uic does (similar to setattr and getattr) so the solution I see is that you convert the .ui to .py with pyuic that will generate a class that You can use in your widget: https://www.riverbankcomputing.com/static/Docs/PyQt5/designer.html#using-the-generated-code – eyllanesc Nov 08 '19 at 16:51
  • @eyllanesc I had not considered this, thanks. – oaky_afterbirth Nov 08 '19 at 16:56
  • @Error-SyntacticalRemorse Ah, I see what you're saying. But if I shouldn't try to force the types for dynamically generated content in Python, is there any way to suggest the intended types to the IDE so that it can attempt type hinting? – oaky_afterbirth Nov 08 '19 at 17:01
  • 1
    I normally don't have said issue using VS Code. (Though PyCharm should be better than VS Code for this). Make sure pylint is installed for PyCharm though. Other than that see the answer below. The reason it may not be type hinting is because of dynamically generated elements during runtime which can be fixed with the below answer. – Error - Syntactical Remorse Nov 08 '19 at 17:03
  • Another solution, if you do not want to convert to .py files is to generate stub method for your window. Discussed in another post [here](https://stackoverflow.com/questions/44739270/pycharm-pyqt-how-to-obtain-code-completion-with-dynamically-loaded-ui-files) – Priyabrata Biswal Jul 27 '20 at 11:44

4 Answers4

2

Code completion does not work for dynamically generated elements as uic does.

One possible solution is to convert the .ui to .py using pyuic and then use that class indicated in Using the Generated Code.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
1

Type annotations can help you out here, just declare a variable for each widget with its type like like this

myBtn: QPushButton = self.ui.myBtn

Then you will get all the intelisence autocompletions like you would if you converted ui to py file

Sven Eberth
  • 3,057
  • 12
  • 24
  • 29
1

I'm using Visual Studio Code as an editor. Just spent a couple of hours Googling, and I think I've finally cracked this.

I've created a button in QT Designer and called it bob_button1 (not sure why, but there you are). I'm using loadUi, because I don't want to have to keep recreating the Python commands corresponding whenever I change my forms:

uic.loadUi(r"C:\__ajb\test.ui",self)

I've created a variable I can use to refer to the button in my code:

self.bob_button1 =  self.findChild(QtWidgets.QPushButton, 'bob_button')

I now want Intellisense to appear when I type self.bob_button1. - like this in fact:

enter image description here

The problem is - how to get VS Code to think of the button as a QPushButton, not the more generic QObject returned from findChild? The answer given elsewhere was to give a type hint:

self.bob_button1 =  self.findChild(QtWidgets.QPushButton, 'bob_button')
self.bob_button1: QPushButton = self.bob_button1

Note that you CAN'T create another variable converting the type of the first to QPushButton, since this will create a duplicate of the first button, and signals and slots won't work for it.

If you try the above in VS Code, it doesn't initially work, so I tried to install PyLint. However, using VS Code extensions this comes up with a release version error message. So I typed this into my terminal window:

pip install pylint

And finally I get Intellisense, and my button click works!

Andy Brown
  • 5,309
  • 4
  • 34
  • 39
  • PS Just checked in PyCharm, and the Intellisense picks up on the type of the widget automatically. Hmmm ... maybe there's benefit in specialisation (PyCharm just does Python; VS Code does every language under the sun). – Andy Brown Jun 01 '22 at 14:39
0

Here is a workaround for code autocomplete. Tested in Pycharm Python38 with pyqt5. Just make sure none of your gui widgets have the name "setupUi" in them.

from distutils.dep_util import newer
from PyQt5 import uic

    # check if UI file is new and needs to be translated to python
    if not newer(self.gui_dir + self.ui_file, self.gui_dir +
                 self.py_file):
        print("UI file has not changed!")
    else:
        # UI file has been updated, open .py file
        fp = open((self.gui_dir + self.py_file), "w")
        # compile .ui file into .py file
        uic.compileUi((self.gui_dir + self.ui_file), fp, execute=True, indent=4, from_imports=True)
        fp.close()
        
        #modify .py file to allow code autocompletion in pycharm
        #gives Ui_MainWindow a constructor so python can perform static analysis
        with open((self.gui_dir + self.py_file), "r") as fp:
            file_data = fp.read().replace('setupUi', '__init__')

        with open((self.gui_dir + self.py_file), 'w') as fp:
            fp.write(file_data)