Vii
Vii

Reputation: 841

Python: How to correctly use external functions

This is something I've seen a fair bit of documentation on but I can't get an exact answer to this specific gap in my knowledge and I keep running into walls because of it, nothing seems to really cover it on this level:

I have a few windows, all of them should be draggable, so I'm building a utilities file. In this file I have this:

def mouseMoveEvent(self, event):
    if self.moving: self.move(event.globalPos()-self.offset)

def mousePressEvent(self, event):
    if event.button() == QtCore.Qt.LeftButton:
        self.moving = True; self.offset = event.pos()


def mouseReleaseEvent(self, event):
    if event.button() == QtCore.Qt.LeftButton:
        self.moving = False

And this will work if I put it into a single file (and don't call from another one) with the actual window.

The question is, how do I call to this from another file (the one that has the UI window)?

I am calling to it by

 from utils import *

So technically those functions are all accessible simply by typing

mouseReleaseEvent(x,x)

But I need the UI to import those functions, so in the main file after putting up the 'from utils import *' I type:

self.moving = False

But that errors no matter what I do. So the question is what is the proper way to enclose the functions listed first (first code block) so that I can call to them from within my UI file?

Here is my UI code:

    #!/usr/bin/env python

    import sys
    import os
    from PyQt4 import QtCore, QtGui
    from vibotCss import css
    from viUtils import *

    class viBadOSUI(QtGui.QDialog):
        def __init__(self):
            QtGui.QDialog.__init__(self)
            #setup window
            self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
            self.resize(300, 150)
            center(self)
            self.setMouseTracking(True)



            #additional code
            self.show()
            self.setStyleSheet(css)



    app = QtGui.QApplication(sys.argv)
    window = viBadOSUI()
    sys.exit(app.exec_())

I have large gaps in my Python knowledge due to learning and using it solely from within Autodesk Maya for a long time - I am trying to bridge said gaps now. Thank you for any help.

Upvotes: 2

Views: 5604

Answers (2)

unutbu
unutbu

Reputation: 880509

I think what you are looking for are mixins:

For example, in viUtils.py you could define:

from PyQt4 import QtCore, QtGui

class Draggable(object):
    def mouseMoveEvent(self, event):
        try:
            if self.moving:
                self.move(event.globalPos()-self.offset)
        except AttributeError:
            pass

    def mousePressEvent(self, event):
        if event.button() == QtCore.Qt.LeftButton:
            self.moving = True; self.offset = event.pos()

    def mouseReleaseEvent(self, event):   
        if event.button() == QtCore.Qt.LeftButton:
            self.moving = False

class Centerable(object):
    pass # add code here

class CountdownTimer(object):
    pass # add code here

By bundling related methods together in mixins, you can add functionality to your classes (windows) by simply adding the mixin(s) to the base classes:

import sys
import os
from PyQt4 import QtCore, QtGui
from vibotCss import css
import viUtils

class viBadOSUI(QtGui.QDialog, 
                viUtils.Draggable, viUtils.CountdownTimer):

    def __init__(self):            
        QtGui.QDialog.__init__(self)
        #setup window
        self.setWindowFlags(QtCore.Qt.FramelessWindowHint)
        self.resize(300, 150)
        center(self)  # You should probably make `center` a method instead of a function and call `self.center()` instead.
        self.setMouseTracking(True)

        #additional code
        self.show()
        self.setStyleSheet(css)

app = QtGui.QApplication(sys.argv)
window = viBadOSUI()
sys.exit(app.exec_())

One thing I do not like about my code above is that I could not figure out how to define a mixin (e.g. Draggable) with an __init__ which gets called automatically by viBadOSUI.__init__. Normally you would use super for this, but it appears QtGui.QDialog.__init__ does not itself call super.

(Actually, I think it might be possible to use super if you put the mixins before QtGui.QDialog, but this relies on trickery which will break once someone forgets that QtGui.QDialog must appear last in the list of bases.)

Because I could not define an __init__ method which would work robustly:

class Draggable(object):
    def __init__(self):
        ...
        self.moving = False

I instead modified mouseMoveEvent to use a try..except. This is better than adding self.moving = False directly to viBadOSUI.__init__ because this attribute is used only by Draggable, and therefore should be somehow bundled with Draggable. Setting the mixin and then also having to remember to initialize an attribute just so the mixin will work is ugly programming.

Using try..except here is perhaps slightly better than using if hasattr(self, 'moving') since the try is quicker as long as an exception is not raised, and the exception will be raised only at the beginning before the first mousePressEvent.

Still, there is room for improvement and I hope someone here will show us a better way.

An alternative approach would have been to define

class viBadOSUI(QtGui.QDialog):
    mouseMoveEvent = viUtils.mouseMoveEvent
    mousePressEvent = viUtils.mousePressEvent
    mouseReleaseEvent = viUtils.mouseReleaseEvent

This gives you a lot of flexibility to add exactly those methods you want from viUtils directly into any class you define. However, I think it is a bit too much flexibility since mouseMoveEvent, mousePressEvent, and mouseReleaseEvent seem "bundled" to me. They all work together to implement a single piece of functionality. This is why I like the mixin approach better. Also, adding viUtils.Draggable to the base classes is less typing than adding those 3 lines to every window subclass that you want to be draggable.

By the way, I'd recommend eschewing from viUtils import * and using import viUtils instead. It will keep your code more organized in the long run.

Upvotes: 4

Ignacio Vazquez-Abrams
Ignacio Vazquez-Abrams

Reputation: 799180

The self (first) argument on a method refers to the instance on which it is bound. The equivalent of self.moving = False outside a method is to bind False to the moving attribute of an instance, e.g. obj.moving = False.

Upvotes: 2

Related Questions