Tuesday, October 11, 2011

PyQt Coding Style Guidelines (Cont.)

One of the aspects of developing consistent code is having a common naming style.  We talked about this for the Designer a bit in the last post - and we'll go into it a bit more in depth now.



Method Conventions

When developing PyQt applications - as mentioned before - its good to follow the naming style that Qt uses for your classes.

  1. There should not be any public member properties - all properties should be accessed via getter and setter methods, and should start with an underscore to denote that they are protected.
  2. Getters are camelHumped, without a 'get' in front.  self.firstName() vs. self.get_first_name() or self.getFirstName()
  3. Getters for boolean properties should start with 'is' or 'has' if it makes sense with the rest of the method name: self.isActive() or self.hasPermissions().  This makes it more understandable that you are getting a boolean back.  Seeing self.permissions() would make you think you'd get a list of permissions back instead of a boolean.
  4. Setters are camelHumped for the same name as the getter method, and start with 'set': self.setFirstName(str) vs. self.set_first_name(str)
  5. Setters for booleans should strip the beginning 'is' or 'has' if it makes sense with the rest of the method name: self.setActive(bool) vs. self.setIsActive(bool).  However, if it is more confusing without it, then it should keep the prefix: self.setHasPermissions(bool) vs. self.setPermissions(bool).
  6. Methods that have to search for a value vs. directly accessing it should begin with 'find': self.findChild(str) if a method looks through a list and returns a result for example, vs. self.child(str) if the method can directly access the member by its name as a key.  This will signal to the developer that one method may be slower than another.
  7. Deletion methods should begin with 'remove', popping methods (that would take an object off of a stack or list and return it but not remove the memory) should begin with 'take': self.removeChild(str) vs. self.takeChild(str).  Removal methods should return either a boolean (success/fail) or an integer (number of items removed) based on what makes logical sense.  Take methods should return either a node, None, or a list of nodes depending on its plurality.
  8. When possible, try to organize your code alphabetically.  This makes it easy to find your methods.  If you get into the proper naming convention habit - this will end up auto-grouping your methods together for the most part.
  9. Never mix return types.  Even though Python is a non-typed language, you shouldn't mix a return type.  If your method returns an object - don't return False if it can't find one - return None.  If you need to return a boolean to flag if something succeeded or failed, then always return that value - return a tuple with the result of the method, and whether or not it succeeded.  This makes it much easier on those using your code to only have to worry about one output type.
Class/Module Structure Conventions

Leveraging Python's package structure will allow you to create contained applications and libraries that span across multiple files.  Generally speaking, when I develop PyQt applications I have found the following guidelines very useful to develop easy to navigate projects:
  1. Classes should always be uppercase and camelhumped.
  2. Name classes to match what type of object they are where possible.  PlaylistTreeWidget, HintLabelEdit, and LoggerBrowser are much more useful and descriptive than Playlist, Hint, and Logger.
  3. Each major class should get its own module, and module names should be the lowercase of its class.  So, PlaylistTreeWidget would be found in the playlisttreewidget.py module.

    Generally speaking, there should be only 1 class per module - unless the other classes are simple helper classes that aren't designed to be referenced by other people using your api.  Internally used classes are ok, as long as they are not complex.
  4. Complex classes should be packaged.  That is to say, if a class requires more than a single module (ui files, modules for other classes, etc.) then that class should be packaged together. 

    For instance, if I have a ClipTreeWidgetItem and a ClipEditWidget as subclasses used by the PlaylistTreeWidget class, and the two widgets each have ui files, I would structure my class as a package, looking like:

    playlisttreewidget/
      |- ui/
      |    |- clipeditwidget.ui
      |     - playlisttreewidget.ui
      |- __init__.py
      |- clipeditwidget.py
      |- cliptreewidgetitem.py
       - playlisttreewidget.py

  5. Usually you should only have 1 ui file per module - matching the ui file name with the module file name.  I like to separate the files into a ui subfolder.  So if I have a PlaylistTreeWidget class in a playlisttreewidget.py module that needs a ui file, I would create a ui/playlisttreewidget.ui sub-folder and file.

    This makes navigation of your project easier.

    For instance, if I had created a module called playlisttreewidget.py that contains both the PlaylistTreeWidget class and the ClipEditWidget class and I see in my ui folder that there is a playlisttreewidget.ui and a clipeditwidget.ui file - then I would have no visual clue from the project structure where the ClipEditWidget class was defined.

    Similarly, if I had created the modules playlisttreewidget.py and clipeditwidget.py in the root project package, or gui package, and not within a sub-packaged playlisttreewidget, then I would have no visual indication that those two classes are connected and should be grouped together.  Following the above guidelines, I would visually know that the playlisttreewidget/clipeditwidget.py contains my CliipEditWidget class that uses a ui file to define its interface in the playlisttreewidget/ui/clipeditwidget.ui and is a part of the overall PlaylistTreeWidget class - all without looking at any of the code.

Widget Conventions

This is a list of all the common Qt widgets that I use and the type conventions that I use when developing with them.

You don't need to follow these if you don't want to (obviously) but I do recommend that you come up with some kind of code that you apply to each and stick with it.  This will least each QWidget, their type code (used in the 'ui_[usage]_[type]' syntax, and an example of each name.

Basic Display Widgets

Found in the Display Widgets group in the Designer's Widget Box, the basic display widgets are used to augment your tool's look and feel.  They are usually in there to provide some additional visual cues to your user (though the QLabel can incorporate more options.  The line is actually a bit decieving because it is actually a QFrame instance that has had its frameShape property set to QFrame.HLine or QFrame.VLine.



QLabel              type: lbl       example: ui_filename_lbl, uiFilenameLbl
Line                type: line      example: ui_break_line,   uiBreakLine


Button Widgets

All of the different button classes that you see in designer under the Buttons group in the widget box inherit from the same base class: QAbstractButton.  As such - there is a lot of commonly shared properties between them.


QPushButton         type: btn       example: ui_submit_btn,   uiSubmitBtn
QToolButtno         type: tbtn      example: ui_options_tbtn, uiOptionsTBtn
QRadioButton        type: rbtn      example: ui_frames_rbtn,  uiFramesRBtn
QCheckBox           type: chk       example: ui_enabled_chk,  uiEnabledChk
QDialogButtonsBox   type: btns      example: ui_dialog_btns,  uiDialogBtns 


Input Widgets

The input widgets provide the application with some input from the user.


QComboBox           type: combo     example: ui_project_combo, uiProjectCombo
QLineEdit           type: edit      example: ui_name_edit,     uiNameEdit
QTextEdit           type: txt       example: ui_details_txt,   uiDetailTxt
QSpinBox            type: spn       example: ui_start_spn,     uiStartSpn
QDoubleSpinBox      type: dspn      example: ui_radius_dspn,   uiRadiusDSpn
QTimeEdit           type: time      example: ui_start_time,    uiStartTime
QDateEdit           type: date      example: ui_start_date,    uiStartDate
QDateTimeEdit       type: dtime     example: ui_start_dtime,   uiStartDTime
QSilder             type: slide     example: ui_radius_slide,  uiRadiusSlide

Container Widgets

The container widget types are the widgets that can contain layouts (as we explored in previous tutorials).  The nature of exactly how these contained widgets interact both with themselves and with the container widget itself depends on the nature of the container.


QWidget             type: widget    example: ui_custom_widget, uiCustomWidget
QFrame              type: frame     example: ui_options_frame, uiOptionsFrame
QGroupBox           type: grp       example: ui_details_grp,   uiDetailsGrp
QScrollArea         type: area      example: ui_options_area,  uiOptionsArea
QStackedWidget      type: stack     example: ui_overlay_stack, uiOverlayStack
QToolBox            type: tbox      example: ui_overlay_tbox,  uiOverlayTBox
QTabWidget          type: tab       example: ui_overlay_tab,   uiOverlayTab
QMdiArea            type: mdi       example: ui_editor_mdi,    uiEditorMdi

Item Widgets vs. Item Views

Qt's built on a Model-View-Controller system for rendering data from your application to a user.  Internally - everything is an Item View by which you map your data (model layer) to be rendered to the user (view layer) and then the developer creates connections between how the user interacts with the data via the control layer.

After a number of tests using PyQt for creating Models and Views, I have experienced very little benefit from using Item Views and implementing Models using Python - if you are displaying custom data and need to implement a custom model.  If you are doing something like displaying the filesystem as a tree or something, you should by all means use the internal Qt Model/View system.

When having to create a custom Python model however, the speed actually seems slower using the View system, and overall it is much more complex to understand and use.  The only time I would recommend using it is in the cases when you need to display the same data as either a table or a tree or a list, when you already have the data existing from somewhere else.  (For instance, creating an abstract database record display) - and even then, you may need to implement it in C++ and wrap it to Python to notice significant performance improvements.

Testing on a 3ds Max scene of 10,000+ items - the QTreeWidget rendered considerably faster than the simple QTreeView and Python Model did, and was no where near as sluggish when scrolling through the tree.  I can only guess that Qt is able to cache the widget based system better than having to hit Python for model data over and over again.

We'll go into some of those tests in a future tutorial as it is far more advanced, so you can test the results for yourself and possibly find some flaws in my tests, however after having developed numerous applications, I have found that the Item Widgets can suffice for everything I have ever needed to develop in a much easier, cleaner and faster way.

In fact, 90% of the time, I can use a QTreeWidget for all of my needs.  It has the columns/cells structure as a QTableWidget would, or can be configured for a single column as the QListWidget would - but allows you to keep your data as a single row instance, vs. controlling the data cell-by-cell.  The other 10% of the time, I would use a QTableWidget and specifically for cases where I have X-Y data, where I want to display information based on the row and the column where the item is - effectively stating that each cell is unique vs. having a row of information about a single object.


QListWidget         type: list      example: ui_options_list,  uiOptionsList
QTreeWidget         type: tree      example: ui_objects_tree,  uiObjectsTree
QTableWidget        type: table     example: ui_coords_table,  uiCoordsTable

QListView           type: listv     example: ui_options_listv, uiOptionsListV
QTreeView           type: treev     example: ui_objects_treev, uiObjectsTreeV
QTableView          type: tablev    example: ui_coords_tablev, uiCoordsTableV

Misc Widgets

These are just some more useful widgets that are available to you out of the box as a developer.  There are a lot more that exist in the API that aren't represented in the Designer so have a look through the docs when you get a chance.

The QGraphicsView class is particularly useful when you need to design custom widgets that you need to control drawing numerous child widgets.  I've written Gantt Schedules, Calendar views, Node-Connection style views, and Graphing views all using this system.

The QWebView is a port of the WebKit project for Qt and is can allow you to embed websites into your applications.  We were able to use it to tie in a couple different websites as a single application - along with desktop widgets for other functionality.  Note: the QWebView is actually from the PyQt4.QtWebKit module - not the PyQt4.QtGui module.


QProgressBar        type: prog      example: ui_status_prog,   uiStatusProg
QScrollBar          type: scroll    example: ui_vert_scroll,   uiVertScroll
QGraphicsView       type: view      example: ui_sched_view,    uiSchedView
QTextBrowser        type: browse    example: ui_result_browse, uiResultBrowse
QWebView            type: web       example: ui_wiki_web,      uiWikiWeb

7 comments:

  1. I know this been posted couple years ago, but I have a question as someone who is divining in pyqt/pyside. Should I always use 'self.my_var = something' or I can just use 'my_var = something' ?

    ReplyDelete
    Replies
    1. there is no OR in this question. these 'self.my_var=something' (you are assigning a class member variable) and 'my_var=something' (you are assigning local variable) are very different things!

      Delete
  2. Hey Adam,

    You will only need to set 'self.my_var' if you are defining a class member. If you are working with local variables within a method, then 'my_var=something' is how you would want to do it. For instance:

    class Pythagorean(object):
    def __init__(self, a, b, c):
    self.a = a # public member
    self._b = b # "protected" member, denoted by preceeding _
    self.__c = c # "private" member, denoted by preceeding __

    def isValid(self):
    # define local variable to improve code flow, however total is not
    # needed to be accessed outside this method
    total = self.a**2 + self._b**2
    return total == self.__c**2

    test = Pythagorean(3, 4, 5)
    print test.isValid() # will print True
    print test.a # will print 3
    print test._b # will print 4 (protected members are still available for access)
    print test.__c # will raise an AttributeError (private members should only be accessed in your methods)


    I would recommend ALWAYS defining your members in your __init__ method. Python will allow you to add attributes to your class within other methods, but this becomes very confusing. Defining them in your __init__ will keep things clean and standardized.

    Also, I generally recommend defining members as protected or private, with getter/setter methods, especially when mixing with PyQt/PySide. This allows you to control your data flow and keep the same standard with their model.

    ReplyDelete
    Replies
    1. Sorry for the spacing, the comments did not properly format the class above. You will need to properly indent the class definition for this example to work.

      Delete
  3. Hej Eric,
    when reading this entry, I had been diving into Python's way of dealing with getters and setters, the property function. What's your opinion on that? You're probably not too fond of it since it doesn't really emulate the Qt/C++-style of code.
    Apart from that, I really like your blog, I've already found tons of useful tips.
    You haven't been posting in a while. Any chance to get another tutorial, say, on QTimer?
    Cheers

    ReplyDelete
  4. I'm not against Python's property system -- I'm just against mixing it into a Qt based application or class. While Python may be the language your plugin is written in, if it is based off Qt (or any other) base framework, I recommend sticking with that system's conventions. The main reason for this is to keep people from guessing what is your code vs. what is Qt's. In this case, I don't use Python properties, I use Qt properties instead.

    ReplyDelete
  5. Can you point me towards where these examples are please? Thx

    ReplyDelete