Monday, October 10, 2011

PyQt Coding Style Guidelines



One of the most important thing a developer can do is develop clean, consistent, code.  Develop a style and follow it.  It will make reading your code much easier for other people, and even yourself, if you have to go back to something you wrote much earlier.

This is a series of coding standards that I follow when I develop PyQt applications - which is a mixture of the Python and Qt guidelines to try to make clean, consistent applications.  Coding style guides are often disagreed on, so take these with a grain of salt - I've thought about them a lot, but you may not agree with my reasoning...which is perfectly good.  The important thing is to develop consistency more than any one style.


from PyQt4.QtGui import *

First and foremost - don't do this.  Ever.   In fact, Python recommends that you never import * from any module.

A lot of exmaples on the internet will start by importing everything from QtCore and QtGui into your module and this is a really bad idea (trust me, I wrote every application this way when I was starting out) because it really bogs down your scope.

What the import * call does is import EVERYTHING from a module scope into your globals scope.  So if you look at the QtGui and QtCore modules - you'll see there is a TON of stuff in there.

If you're trying to get a QPushButton, why waste the time to import 10,000+ other symbols?

Instead - only import what you need.  from PyQt4.QtGui import QDialog over from PyQt4.QtGui import * everytime.

If you're looking for the ability to access any QtGui member on the fly without having to go back to the top of your module and add a particular import, just import the module itself and access the members from there: from PyQt4 import QtGui and QtGui.QDialog.  This may be more typing - but it is a far better practice.

Pep-8 Standards vs. Qt Standards


Generally speaking - I would say you should try to follow global community standards when developing.

Python is very good about trying to develop a consistent style to development, and if you haven't read through their Pep-8 Guidelines - I would recommend doing so...it will make you a much better Python programmer, and help poise yourself to make contributions to the open-source community (should you be able to).

Qt is also verty good about developing a consistent style to their libraries.  Their code is very easy to read because of it, and as you get a nack for their conventions, methods just appear where they should - even if you didn't know that they would be there.  If it logically makes sense that a function would exist, odds are it does.

When developing PyQt applications however, there is a slight conflict with the Qt style and Python style syntaxes.  Python prefers to lowercase, underscored functions, while Qt prefers to camel hump functions.  In this case, I personally have found that it is preferable to defer to the Qt syntax over the Python syntax.

While you may be programming in Python, you're creating Qt classes - inheriting their conventions along the way.  When adding a new method, you should keep the consistency of Qt - that way someone working with your widgets doesn't have to think about whether a method came from Python, and so use underscores, or came from Qt itself, and so use camel humps.

For instance, if I make a new widget class, it would be preferable to do this:

class MyWidget(QtGui.QWidget):
    def loadItems(self):
        print 'loading items'
        
    def refreshResuls(self):
        print 'refreshing results'

Over this:

class MyWidget(QtGui.QWidget):
    def load_items(self):
        print 'loading items'
        
    def refresh_results(self):
        print 'refreshing results'

Event though the Python guidelines do not recommend it, because it is easier to read:

widget = MyWidget()
widget.setWindowTitle('Testng')
widget.loadItems()
widget.setMaximumWidth(100)
widget.refreshResults()

Than this:

widget = MyWidget()
widget.setWindowTitle('Testing')
widget.load_items()
widget.setMaximumWidth(100)
widget.refresh_results()


Non-Qt components to your Qt application...you can manage based on your own company or style preference - however, be consistent!  That is more important than anything else.  Don't have 1 set of libraries underscored, and the next camel humped.  I personally prefer to keep the consistency through the whole project - a purely Python project would follow the Pep-8 standards, but if a project uses Qt - then I would make the whole project follow the Qt standards.

Getter and Setter Guidelines

Generally speaking, you should never expose public members in your Qt classes.  In Python, there isn't really a public/protect/private syntax as there is in C++ in that every member can be accessed - its just part of the language - however there is an implied system. 

Python uses underscores to flag different types of data.
  1. No underscores: public variable/member
  2. 1 opening underscore: protected variable/member
  3. 1 trailing underscore: used when defining a variable that would otherwise clash with a Python internal
  4. 2 opening underscores: private variable/member (does some funkiness under the hood so it becomes pretty hard to access from outside your class or module)
  5. 2 opening underscores and 2 trailing underscores: built-in, usually used by internal python variables and methods
When developing new Qt objects, widgets, or classes you should create protected members, and allow access to them via getter and setter methods.

While Python would often do this via its @property macro - Qt always accesses its members with methods directly, and so your code should follow suit - and again, your methods should be camel cased vs. underscored: value(), setValue(int), vs. get_value(), set_value(int).

It is preferable to write a widget like this:

class CustomWidget(QtGui.QWidget):
    def __init__( self ):
        super(CustomWidget, self).__init__()
        
        self._value = 10
        
    def loadItems(self):
        print 'loading items'
        
    def refreshResuls(self):
        print 'refreshing results'
    
    def setValue( self, value ):
        self._value = value
    
    def value( self ):
        return self._value

Than it is to write it like this:

class MyWidget(QtGui.QWidget):
    def __init__( self ):
        super(CustomWidget, self).__init__()
        
        self.value = 10
    
    def get_value( self ):
        return self.value
    
    def load_items(self):
        print 'loading items'
        
    def refresh_results(self):
        print 'refreshing results'
    
    def set_value( self, value ):
        self.value = value

For the same consistency reasoning.

Warning: It can also easily happen that you overwrite an internal Qt method that you didn't know existed.   Because you're inheriting from a Qt class, if you are unsure if your member variable already exists, you should check the 'List all members' docs for the Qt class you inherit from.  Don't redefine something like parent or height or you could seriously break some internal methods.  C++ solves this problem by only virtualizing the methods a developer wants to expose for overridding - however, in Python, you can override everything - which can be good or bad.

Protect your UI Members

Whether you agree with protecting all of your members and providing accessor methods to them, you should at least always protect your interface compontents.

Interfaces are subject to change frequently - and if you are developing libraries of widgets in particular, you want to create a separation between what you expose to other developers from your classes via a method layer.  If you need to change the way a widget is loaded, what structure the view is in - it should be invisible to your API.


It becomes much easier to control your inputs and outputs from the beginning and controlling the flow of information via events (we'll touch on this more later) than passing around raw data.  If you get in this habit early, then it becomes much easier to build expandible, modular code.  Never allow a developer to directly access one of your gui objects - if you want to let them have access to a child widget of your class - provide them a method instead.

Designer Members vs. Programmatic Members

When working with designer files, conventions become very important for a couple of reasons.  The most important of which is that you are removing the definition of your member from your code.

That means that any other developer reading through your file will not be able to jump to its definition in the code itself - but somehow has to realize that it was defined in an external file - and possibly has to load that external file to find out what kind of member it is.

If I create a QComboBox in my Designer file and call it 'project', when I load the file into my code - I can now access self.project as a member.

This is dangerous because you have now exposed a user interface variable to the developer, and there is absolutely no way for a developer to know what that variable refers to, or where it was created.  They would have to infer from its usage what it is, and by process of elimination, determine that it came from your designer file.

Even more dangerous - try making a QPushButton and calling it 'show'.  This will actually replace your QWidget.show method with your button - and now your whole application is hosed.

Its better to get into a good naming convention habit from your very first application on.  There are 3 parts to a well named Designer component:
  1. Something about the name should immediately let you know that it was created in designer vs. in code.
  2. A part of the name should describe what the object is used for ('show', 'project', etc.)
  3. A part of the name should describe what the object actually is
If you follow these guidelines - then any developer reading your code will immediately be able to tell what the member is, where it came from, and should have a logical guess as to why it exists.

Lets go back to our combo box example.  If instead of calling it 'project', I call it 'ui_project_combo', I now have a much more usable name for that component.  If I know by heart what methods a QComboBox has (which you will get to over time), you don't need to read any further than that one line of code to know your options.

Generally speaking - the convention that I use for every ui component I make in designer, is either 'ui_[usage]_[type]' or 'ui[Usage][Type]' depending on if you are using Python standards or Qt standards.  (Since these will be considered protected/private members, it is not as important which syntax you prefer to use - as long as its consistent!)

If I am making a ui component via code - I simply strip off the beginning 'ui' - and name it '_[usage]_[type]' or '_[usage][Type]'  - again - the intro underscores denoting a protected member.

For a list of the different Qt widgets and the types that I personally use, see the next post.

Using self.ui_* over self.ui.*

Another popular way that you can load a ui file into your widget, is to store the resulting variable from the PyQt4.uic.loadUi function call as a ui variable.

This removes the need to define 'ui_' as the beginning for each of your members in the Designer file, with the result looking like:

#!/usr/bin/python ~/workspace/pyqt/benchmark/loadui_simple.py

import PyQt4.uic
from PyQt4 import QtCore, QtGui

class MainWindow(QtGui.QMainWindow):
    def __init__(self, parent = None):
        super(MainWindow, self).__init__(parent)
        
        # load the ui
        self.ui = PyQt4.uic.loadUi('ui/mainwindow.ui', self)
        
        # set the actions
        self.ui.exec_btn.setDefaultAction( self.ui.exec_act )
        
if ( __name__ == '__main__' ):
    app = None
    if ( not app ):
        app = QtGui.QApplication([])
    
    window = MainWindow()
    window.show()
    
    if ( app ):
        app.exec_()

vs.

#!/usr/bin/python ~/workspace/pyqt/benchmark/loadui_simple.py

import PyQt4.uic
from PyQt4 import QtCore, QtGui

class MainWindow(QtGui.QMainWindow):
    def __init__(self, parent = None):
        super(MainWindow, self).__init__(parent)
        
        # load the ui
        PyQt4.uic.loadUi('ui/mainwindow.ui', self)
        
        # set the actions
        self.ui_exec_btn.setDefaultAction( self.ui_exec_act )
        
if ( __name__ == '__main__' ):
    app = None
    if ( not app ):
        app = QtGui.QApplication([])
    
    window = MainWindow()
    window.show()
    
    if ( app ):
        app.exec_()

While this accomplishes the goal set from the last section - it is now obvious where a variable came from (ui vs. programmatic) - it is slightly deceptive.   While you can store the result for the loadUI method, and access the members that way - you don't have to.  Those members will still be added to the main self instance without any reference back to their ui.

Namely, self.ui.exec_btn == self.exec_btn.  If someone is using an auto-completion for instance on one of your widgets - they may think that they have access to something you unintended.  This is a bit nit-picky - overall I think this is a pretty good technique - but if you use it, just be consciously aware that this is actually happening.

If for instance you choose to ignore the recommendation to include the type as part of the component name and call your button show again, while you will have self.ui.show as your intended button - you will still blow away the base QWidget.show method unintentionally.




In the next tutorial - I'll go over the different QWidget classes, and some examples of the naming conventions that I use when working with them.

6 comments:

  1. Thanks for writing this, hopefully you'll do more..there aren't enough of these types of tutorials published!

    ReplyDelete
  2. I'm glad to do it and finally have a chance to. If you (or anyone reading these) has any particular questions or requests - feel free to add them and I'll try to answer when I can.

    ReplyDelete
  3. glad to read your post. Helped me a lot.

    ReplyDelete
  4. I've been websearching for conventions for documenting PyQt5 signals and slots. e.g. If I declare this signal:

    localRootChanged = pyqtSignal(str, str)

    Is there a convention for which is the old value and which is the new value? Is there a convention for how to document these parameters, and more information about the signal?

    ReplyDelete
  5. This comment has been removed by the author.

    ReplyDelete