tl;dr -- In a PySide application, an object whose method throws an exception will remain alive even when all other references have been deleted. Why? And what, if anything, should one do about this?
In the course of building a simple CRUDish app using a Model-View-Presenter architecture with a PySide GUI, I discovered some curious behavior. In my case:
The interface is divided into multiple Views -- i.e., each tab page displaying a different aspect of data might be its own class of View
Views are instantiated first, and in their initialization, they instantiate their own Presenter, keeping a normal reference to it
A Presenter receives a reference to the View it drives, but stores this as a weak reference (weakref.ref) to avoid circularity
No other strong references to a Presenter exist. (Presenters can communicate indirectly with the pypubsub messaging library, but this also stores only weak references to listeners, and is not a factor in the MCVE below.)
Thus, in normal operation, when a View is deleted (e.g., when a tab is closed), its Presenter is subsequently deleted as its reference count becomes 0
However, a Presenter of which a method has thrown an exception does not get deleted as expected. The application continues to function, because PySide employs some magic to catch exceptions. The Presenter in question continues to receive and respond to any View events bound to it. But when the View is deleted, the exception-throwing Presenter remains alive until the whole application is closed. An MCVE (link for readability):
import logging
import sys
import weakref
from PySide import QtGui
class InnerPresenter:
def __init__(self, view):
self._view = weakref.ref(view)
self.logger = logging.getLogger('InnerPresenter')
self.logger.debug('Initializing InnerPresenter (id:%s)' % id(self))
def __del__(self):
self.logger.debug('Deleting InnerPresenter (id:%s)' % id(self))
#property
def view(self):
return self._view()
def on_alert(self):
self.view.show_alert()
def on_raise_exception(self):
raise Exception('From InnerPresenter (id:%s)' % id(self))
class OuterView(QtGui.QMainWindow):
def __init__(self, *args, **kwargs):
super(OuterView, self).__init__(*args, **kwargs)
self.logger = logging.getLogger('OuterView')
# Menus
menu_bar = self.menuBar()
test_menu = menu_bar.addMenu('&Test')
self.open_action = QtGui.QAction('&Open inner', self, triggered=self.on_open, enabled=True)
test_menu.addAction(self.open_action)
self.close_action = QtGui.QAction('&Close inner', self, triggered=self.on_close, enabled=False)
test_menu.addAction(self.close_action)
def closeEvent(self, event, *args, **kwargs):
self.logger.debug('Exiting application')
event.accept()
def on_open(self):
self.setCentralWidget(InnerView(self))
self.open_action.setEnabled(False)
self.close_action.setEnabled(True)
def on_close(self):
self.setCentralWidget(None)
self.open_action.setEnabled(True)
self.close_action.setEnabled(False)
class InnerView(QtGui.QWidget):
def __init__(self, *args, **kwargs):
super(InnerView, self).__init__(*args, **kwargs)
self.logger = logging.getLogger('InnerView')
self.logger.debug('Initializing InnerView (id:%s)' % id(self))
self.presenter = InnerPresenter(self)
# Layout
layout = QtGui.QHBoxLayout(self)
alert_button = QtGui.QPushButton('Alert!', self, clicked=self.presenter.on_alert)
layout.addWidget(alert_button)
raise_button = QtGui.QPushButton('Raise exception!', self, clicked=self.presenter.on_raise_exception)
layout.addWidget(raise_button)
self.setLayout(layout)
def __del__(self):
super(InnerView, self).__del__()
self.logger.debug('Deleting InnerView (id:%s)' % id(self))
def show_alert(self):
QtGui.QMessageBox(text='Here is an alert').exec_()
if __name__ == '__main__':
logging.basicConfig(level=logging.DEBUG)
app = QtGui.QApplication(sys.argv)
view = OuterView()
view.show()
sys.exit(app.exec_())
Open and close the inner view, and you'll see both view and presenter are deleted as expected. Open the inner view, click the button to trigger an exception on the presenter, then close the inner view. The view will be deleted, but the presenter won't until the application exits.
Why? Presumably whatever it is that catches all exceptions on behalf of PySide is storing a reference to the object that threw it. Why would it need to do that?
How should I proceed (aside from writing code that never causes exceptions, of course)? I have enough sense not to rely on __del__ for resource management. I get that I have no right to expect anything subsequent to a caught-but-not-really-handled exception to go ideally but this just strikes me as unnecessarily ugly. How should I approach this in general?
The problem is sys.last_tracback and sys.last_value.
When a traceback is raised interactively, and this seems to be what is emulated, the last exception and its traceback are stores in sys.last_value and sys.last_traceback respectively.
Doing
del sys.last_value
del sys.last_traceback
# for consistency, see
# https://docs.python.org/3/library/sys.html#sys.last_type
del sys.last_type
will free the memory.
It's worth noting that at most one exception and traceback pair can get cached. This means that, because you're sane and don't rely on del, there isn't a massive amount of damage to be done.
But if you want to reclaim the memory, just delete those values.
Related
When Delphi introduced actions some years ago now, one of the great advantages was that the status (enabled/disabled) of the action was handled in the event loop, rather than that the programmer had to think about updating these states concommittant to all status changes of the application. For example:
procedure TMyForm.SaveActionUpdate(Sender: TObject)
begin
(sender as TAction).enabled := self.contentHasChanged();
end;
instead of
procedure TMyForm.onContentChanging(sender: TObject)
begin
SaveAction.enabled := True;
SaveAsAction.enabled := True;
RevertAction ... etc.
end;
Now I am trying to rebuild some of these old Delphi programs in Python using Qt, but so far I have failed to figure out how to enable QActions without resorting to explicitly setting it enabled whenever my content changes. What would be a fruitful approach to do this?
In C++:
void MyForm::onSaveActionUpdateSlot() // must be a slot
{
QAction* pAction = qobject_cast<QAction*>( sender() );
if (pAction) // if sent by QAction
pAction->setEnbaled( this->contentHasChanged() );
}
I dived into the VCL to see how it's implemented there. I hesitated whether it would be a good idea to do so, for the concept op this framework may be well different from Qt and put me on the wrong track rather than being helpful. However, the lack of response to this question suggests to me that Qt actions just may not work this way. Which is a pity.
Delphi's VCL actions register themselves on construction in a central list that gets evaluated in the application's idle time in the event loop. This at least I found can be emulated in Qt by using a timer started with timeout 0. Thus I come to this work-around:
class HFClient(QMainWindow):
def updateAction(self, action):
action.setEnabled(False)
class ActionUpdater(QTimer):
def __init__(self):
super(ActionUpdater, self).__init__()
self.members = {}
self.timeout.connect(self.timerEvent)
def add(self, action, updateProc):
self.members[action] = updateProc
def remove(self, action):
del self.members[action]
def timerEvent(self, unused):
# prevent being the last one to keep the object alive:
done = [action for action in self.members if sys.getrefcount(action) < 5]
for action in done:
self.remove(action)
# call registered method:
for action, updateProc in self.members.items():
updateProc(action)
if __name__ == '__main__':
app = QApplication(sys.argv)
w = HFClient()
w.show()
updater = ActionUpdater()
updater.start()
a = QAction("save as", app)
updater.add(a, w.updateAction)
# del a
app.exec()
Memory managed programming languages are a bit hard to have the objects unregistered again when the registered object is done for. All too easily the object is kept alive because it still has a reference in the register. I tried to circumvent this by checking the refcount vs. the number of references within the updater instance. I would value a better method for this, though.
One could make a QAction descendant that would register itself (and maybe a signal slot combination for updating).
UPDATE
To use the conventional signal-slot mechanism, descend from Action like
class UpdatedAction(QAction):
update = pyqtSignal(QAction)
def getUpdated(self):
self.update.emit(self)
Weakrefs solve the reference counting issue in the code above:
class ActionUpdater(QTimer):
def __init__(self):
super(ActionUpdater, self).__init__()
self.members = []
self.timeout.connect(self.timerEvent)
def add(self, action):
self.members.append(weakref.ref(action, self.remove))
def remove(self, action):
self.members.remove(action)
def timerEvent(self, unused):
# call registered method:
for action in self.members:
action().getUpdated()
I'm trying to create a simple threaded application whereby i have a method which does some long processing and a widget that displays a loading bar and cancel button.
My problem is that no matter how i implement the threading it doesn't actually thread - the UI is locked up once the thread kicks in. I've read every tutorial and post about this and i'm now resorting on asking the community to try and solve my problem as i'm at a loss!
Initially i tried subclassing QThread until the internet said this was wrong. I then attempted the moveToThread approach but it made zero difference.
Initialization code:
loadingThreadObject = LoadThread(arg1)
loadingThread = PythonThread()
loadingThreadObject.moveToThread(loadingThread)
loadingThread.started.connect(loadingThreadObject.load)
loadingThread.start()
PythonThread class (apparently QThreads are bugged in pyQt and don't start unless you do this):
class PythonThread (QtCore.QThread):
def __init__(self, parent=None):
QtCore.QThread.__init__(self, parent)
def start(self):
QtCore.QThread.start(self)
def run(self):
QtCore.QThread.run(self)
LoadThread class:
class LoadThread (QtCore.QObject):
results = QtCore.Signal(tuple)
def __init__ (self, arg):
# Init QObject
super(QtCore.QObject, self).__init__()
# Store the argument
self.arg = arg
def load (self):
#
# Some heavy lifting is done
#
loaded = True
errors = []
# Emits the results
self.results.emit((loaded, errors))
Any help is greatly appreciated!
Thanks.
Ben.
The problem was with the SQL library I was using (a custom in-house solution) which turned out not to be thread safe and thus performed blocking queries.
If you are having a similar problem, first try removing the SQL calls and seeing if it still blocks. If that solves the blocking issue, try reintroducing your queries using raw SQL via MySQLdb (or the equivalent for the type of DB you're using). This will diagnose whether or not the problem is with your choice of SQL library.
The function connected to the started signal will run the thread which it was connected, the main GUI thread. However, a QThread's start() function executes its run() method in the thread after the thread is initialized so a subclass of QThread should be created and its run method should run LoadThread.load, the function you want to execute. Don't inherit from PythonThread, there's no need for that. The QThread subclass's start() method should be used to start the thread.
PS: Since in this case the subclass of QThread's run() method only calls LoadThread.load(), the run() method could be simply set to LoadThread.load:
class MyThread(QtCore.QThread):
run = LoadThread.load # x = y in the class block sets the class's x variable to y
An example:
import time
from PyQt4 import QtCore, QtGui
import sys
application = QtGui.QApplication(sys.argv)
class LoadThread (QtCore.QObject):
results = QtCore.pyqtSignal(tuple)
def __init__ (self, arg):
# Init QObject
super(QtCore.QObject, self).__init__()
# Store the argument
self.arg = arg
def load(self):
#
# Some heavy lifting is done
#
time.sleep(5)
loaded = True
errors = []
# Emits the results
self.results.emit((loaded, errors))
l = LoadThread("test")
class MyThread(QtCore.QThread):
run = l.load
thread = MyThread()
button = QtGui.QPushButton("Do 5 virtual push-ups")
button.clicked.connect(thread.start)
button.show()
l.results.connect(lambda:button.setText("Phew! Push ups done"))
application.exec_()
A friend and I have been playing around with pygame some and came across this tutorial for building games using pygame. We really liked how it broke out the game into a model-view-controller system with events as a go-between, but the code makes heavy use of isinstance checks for the event system.
Example:
class CPUSpinnerController:
...
def Notify(self, event):
if isinstance( event, QuitEvent ):
self.keepGoing = 0
This results in some extremely unpythonic code. Does anyone have any suggestions on how this could be improved? Or an alternative methodology for implementing MVC?
This is a bit of code I wrote based on #Mark-Hildreth answer (how do I link users?) Does anyone else have any good suggestions? I'm going to leave this open for another day or so before picking a solution.
class EventManager:
def __init__(self):
from weakref import WeakKeyDictionary
self.listeners = WeakKeyDictionary()
def add(self, listener):
self.listeners[ listener ] = 1
def remove(self, listener):
del self.listeners[ listener ]
def post(self, event):
print "post event %s" % event.name
for listener in self.listeners.keys():
listener.notify(event)
class Listener:
def __init__(self, event_mgr=None):
if event_mgr is not None:
event_mgr.add(self)
def notify(self, event):
event(self)
class Event:
def __init__(self, name="Generic Event"):
self.name = name
def __call__(self, controller):
pass
class QuitEvent(Event):
def __init__(self):
Event.__init__(self, "Quit")
def __call__(self, listener):
listener.exit(self)
class RunController(Listener):
def __init__(self, event_mgr):
Listener.__init__(self, event_mgr)
self.running = True
self.event_mgr = event_mgr
def exit(self, event):
print "exit called"
self.running = False
def run(self):
print "run called"
while self.running:
event = QuitEvent()
self.event_mgr.post(event)
em = EventManager()
run = RunController(em)
run.run()
This is another build using the examples from #Paul - impressively simple!
class WeakBoundMethod:
def __init__(self, meth):
import weakref
self._self = weakref.ref(meth.__self__)
self._func = meth.__func__
def __call__(self, *args, **kwargs):
self._func(self._self(), *args, **kwargs)
class EventManager:
def __init__(self):
# does this actually do anything?
self._listeners = { None : [ None ] }
def add(self, eventClass, listener):
print "add %s" % eventClass.__name__
key = eventClass.__name__
if (hasattr(listener, '__self__') and
hasattr(listener, '__func__')):
listener = WeakBoundMethod(listener)
try:
self._listeners[key].append(listener)
except KeyError:
# why did you not need this in your code?
self._listeners[key] = [listener]
print "add count %s" % len(self._listeners[key])
def remove(self, eventClass, listener):
key = eventClass.__name__
self._listeners[key].remove(listener)
def post(self, event):
eventClass = event.__class__
key = eventClass.__name__
print "post event %s (keys %s)" % (
key, len(self._listeners[key]))
for listener in self._listeners[key]:
listener(event)
class Event:
pass
class QuitEvent(Event):
pass
class RunController:
def __init__(self, event_mgr):
event_mgr.add(QuitEvent, self.exit)
self.running = True
self.event_mgr = event_mgr
def exit(self, event):
print "exit called"
self.running = False
def run(self):
print "run called"
while self.running:
event = QuitEvent()
self.event_mgr.post(event)
em = EventManager()
run = RunController(em)
run.run()
A cleaner way of handling events (and also a lot faster, but possibly consumes a bit more memory) is to have multiple event handler functions in your code. Something along these lines:
The Desired Interface
class KeyboardEvent:
pass
class MouseEvent:
pass
class NotifyThisClass:
def __init__(self, event_dispatcher):
self.ed = event_dispatcher
self.ed.add(KeyboardEvent, self.on_keyboard_event)
self.ed.add(MouseEvent, self.on_mouse_event)
def __del__(self):
self.ed.remove(KeyboardEvent, self.on_keyboard_event)
self.ed.remove(MouseEvent, self.on_mouse_event)
def on_keyboard_event(self, event):
pass
def on_mouse_event(self, event):
pass
Here, the __init__ method receives an EventDispatcher as an argument. The EventDispatcher.add function now takes the type of the event you are interested in, and the listener.
This has benefits for efficiency since the listener only ever gets called for events that it is interested in. It also results in more generic code inside the EventDispatcher itself:
EventDispatcher Implementation
class EventDispatcher:
def __init__(self):
# Dict that maps event types to lists of listeners
self._listeners = dict()
def add(self, eventcls, listener):
self._listeners.setdefault(eventcls, list()).append(listener)
def post(self, event):
try:
for listener in self._listeners[event.__class__]:
listener(event)
except KeyError:
pass # No listener interested in this event
But there is a problem with this implementation. Inside NotifyThisClass you do this:
self.ed.add(KeyboardEvent, self.on_keyboard_event)
The problem is with self.on_keyboard_event: it is a bound method which you passed to the EventDispatcher. Bound methods hold a reference to self; this means that as long as the EventDispatcher has the bound method, self will not be deleted.
WeakBoundMethod
You will need to create a WeakBoundMethod class that holds only a weak reference to self (I see you already know about weak references) so that the EventDispatcher does not prevent the deletion of self.
An alternative would be to have a NotifyThisClass.remove_listeners function that you call before deleting the object, but that's not really the cleanest solution and I find it very error prone (easy to forget to do).
The implementation of WeakBoundMethod would look something like this:
class WeakBoundMethod:
def __init__(self, meth):
self._self = weakref.ref(meth.__self__)
self._func = meth.__func__
def __call__(self, *args, **kwargs):
self._func(self._self(), *args, **kwargs)
Here's a more robust implementation I posted on CodeReview, and here's an example of how you'd use the class:
from weak_bound_method import WeakBoundMethod as Wbm
class NotifyThisClass:
def __init__(self, event_dispatcher):
self.ed = event_dispatcher
self.ed.add(KeyboardEvent, Wbm(self.on_keyboard_event))
self.ed.add(MouseEvent, Wbm(self.on_mouse_event))
Connection Objects (Optional)
When removing listeners from the manager/ dispatcher, instead of making the EventDispatcher needlessly search through the listeners until it finds the right event type, then search through the list until it finds the right listener, you could have something like this:
class NotifyThisClass:
def __init__(self, event_dispatcher):
self.ed = event_dispatcher
self._connections = [
self.ed.add(KeyboardEvent, Wbm(self.on_keyboard_event)),
self.ed.add(MouseEvent, Wbm(self.on_mouse_event))
]
Here EventDispatcher.add returns a Connection object that knows where in the EventDispatcher's dict of lists it resides. When a NotifyThisClass object is deleted, so is self._connections, which will call Connection.__del__, which will remove the listener from the EventDispatcher.
This could make your code both faster and easier to use because you only have to explicitly add the functions, they are removed automatically, but it's up to you to decide if you want to do this. If you do it, note that EventDispatcher.remove shouldn't exist anymore.
I stumbled upon SJ Brown's tutorial on making games in the past. It's a great page, one of the best I've read. However, like you, I didn't like the calls to isinstance, or the fact that all the listeners receive all the events.
First, isinstance is slower than checking that two strings are equals, so I ended up storing a name on my events and test for the name rather than the class. But still, the notify function with its battery of if was itching me because it felt like a waste of time. We can do two optimizations here:
Most listeners are interested in only a few types of events. For performance reasons, when QuitEvent is posted, only the listeners interested in it should be notified. The event manager keeps track of which listener wants to listen to which event.
Then, to avoid going through a tons of if statements in a single notify method, we will have one method per type of event.
Example:
class GameLoopController(...):
...
def onQuitEvent(self, event):
# Directly called by the event manager when a QuitEvent is posted.
# I call this an event handler.
self._running = False
Because I want the developer to type as little as possible, I made the following thing:
When a listener is registered to an event manager, the event manager scans all the methods of the listener. When one method starts with 'on' (or any prefix you like), then it looks at the rest ("QuitEvent") and binds this name to this method. Later, when the event manager pumps its event list, it looks at the event class name: "QuitEvent". It knows that name, and therefore can directly call all the corresponding event handlers directly. The developer has nothing to do but adding onWhateverEvent methods to have them working.
It has some drawbacks:
If I make a typo in the name of the handler ("onRunPhysicsEvent"
instead of "onPhysicsRanEvent" for example") then my handler will
never be called and I'll wonder why. But I know the trick so I
don't wonder why very long.
I cannot add an event handler after the listener has been
registered. I must un-register and re-register. Indeed, the
events handlers are scanned only during the registration. Then
again, I never had to do that anyway so I don't miss it.
Despite these drawbacks I like it much more than having the constructor of the listener explicitly explain the event manager that it wants to stay tuned of this, this, this and this event. And it's the same execution speed anyway.
Second point:
When designing our event manager, we want to be careful. Very often, a listener will respond to an event by creating-registering or unregistering-destroying listeners. This happens all the time. If we don't think about it then our game may break with RuntimeError: dictionary changed size during iteration. The code that you propose iterates over a copy of the dictionary so you're protected against explosions; but it has consequences to be aware of:
- Listeners registered because of an event will not receive that event.
- Listeners unregistered because of an event will still receive that
event.
I never found it to be a problem though.
I implemented that myself for the game I am developing. I can link you to two articles and a half I wrote on the subject:
http://niriel.wordpress.com/2011/08/06/who-controls-the-controllers/
http://niriel.wordpress.com/2011/08/08/the-event-management-is-in-place/
http://niriel.wordpress.com/2011/08/11/the-first-screenshot-of-infiniworld/
The links to my github account will bring you directly to the source code of the relevant parts. If you cannot wait, here's the thing: https://github.com/Niriel/Infiniworld/blob/v0.0.2/src/evtman.py . In there you'll see that the code for my event class is a bit big, but that every inherited event is declared in 2 lines: the base Event class is making your life easy.
So, this all works using python's introspection mechanism, and using the fact that methods are objects like any other that can be put in dictionaries. I think it's quite pythony :).
Give each event a method (possibly even using __call__), and pass in the Controller object as an argument. The "call" method should then call the controller object. For example...
class QuitEvent:
...
def __call__(self, controller):
controller.on_quit(self) # or possibly... controller.on_quit(self.val1, self.val2)
class CPUSpinnerController:
...
def on_quit(self, event):
...
Whatever code you're using to route your events to your controllers will call the __call__ method with the correct controller.
I stumbled upon the same issue (almost a decade later!), and here is an implementation I've been using so that the EventManager would notify only a subset of listeners.
It is based on defaultdict: the _listeners attribute of EventManager is a defaultdict of WeakKeyDictionary().
Event are all inherited from an empty abstract Event class, so listeners can focus only on some classes of events they want to listen to.
Here is a minimalist code to get the idea behind it:
from collections import defaultdict
from weakref import WeakKeyDictionary
class Event:
def __init__(self):
pass
class KeyboardEvent(Event): # for instance, a keyboard event class with the key pressed
def __init__(self, key):
self._key = key
class EventManager:
def __init__(self):
self._listeners = defaultdict(lambda: WeakKeyDictionary())
def register_listener(self, event_types, listener):
for event_type in event_types:
self._listeners[event_type][listener] = 1
def unregister_listener(self, listener):
for event_type in self._listeners:
self._listeners[event_type].pop(listener, None)
def post_event(self, event):
for listener in self._listeners[event.__class__]:
listener.notify(event)
When registering, a listener tells the event manager which event type it wants to be notified of.
When posting events, the event manager will only notify the listeners which registered to be notified for that type of event.
This piece of code has, of course, much less scope than the very general (and very elegant) solution proposed by #Paul Manta, but in my case, it helped remove some repetitive calls to isinstance and other checks while keeping things as simple as I could.
One of the drawbacks of this is that all type of events have to be objects of some class, but in OO python, this is supposed to be the way to go.
I am updating some code from using libglade to GtkBuilder, which is supposed to be the way of the future.
With gtk.glade, you could call glade_xml.signal_autoconnect(...) repeatedly to connect signals onto objects of different classes corresponding to different windows in the program. However Builder.connect_signals seems to work only once, and (therefore) to give warnings about any handlers that aren't defined in the first class that's passed in.
I realize I can connect them manually but this seems a bit laborious. (Or for that matter I could use some getattr hackery to let it connect them through a proxy to all the objects...)
Is it a bug there's no function to hook up handlers across multiple objects? Or am I missing something?
Someone else has a similar problem http://www.gtkforums.com/about1514.html which I assume means this can't be done.
Here's what I currently have. Feel free to use it, or to suggest something better:
class HandlerFinder(object):
"""Searches for handler implementations across multiple objects.
"""
# See <http://stackoverflow.com/questions/4637792> for why this is
# necessary.
def __init__(self, backing_objects):
self.backing_objects = backing_objects
def __getattr__(self, name):
for o in self.backing_objects:
if hasattr(o, name):
return getattr(o, name)
else:
raise AttributeError("%r not found on any of %r"
% (name, self.backing_objects))
I have been looking for a solution to this for some time and found that it can be done by passing a dict of all the handlers to connect_signals.
The inspect module can extract methods using
inspect.getmembers(instance, predicate=inspect.ismethod
These can then be concatenated into a dictionary using d.update(d3), watching out for duplicate functions such as on_delete.
Example code:
import inspect
...
handlers = {}
for c in [win2, win3, win4, self]: # self is the main window
methods = inspect.getmembers(c, predicate=inspect.ismethod)
handlers.update(methods)
builder.connect_signals(handlers)
This will not pick up alias method names declared using #alias. For an example of how to do that, see the code for Builder.py, at def dict_from_callback_obj.
I'm only a novice but this is what I do, maybe it can inspire;-)
I instantiate the major components from a 'control' and pass the builder object so that the instantiated object can make use of any of the builder objects (mainwindow in example) or add to the builder (aboutDialog example). I also pass a dictionary (dic) where each component adds "signals" to it.
Then the 'connect_signals(dic)' is executed.
Of course I need to do some manual signal connecting when I need to pass user arguments to the callback method, but those are few.
#modules.control.py
class Control:
def __init__(self):
# Load the builder obj
guibuilder = gtk.Builder()
guibuilder.add_from_file("gui/mainwindow.ui")
# Create a dictionnary to store signal from loaded components
dic = {}
# Instanciate the components...
aboutdialog = modules.aboutdialog.AboutDialog(guibuilder, dic)
mainwin = modules.mainwindow.MainWindow(guibuilder, dic, self)
...
guibuilder.connect_signals(dic)
del dic
#modules/aboutdialog.py
class AboutDialog:
def __init__(self, builder, dic):
dic["on_OpenAboutWindow_activate"] = self.on_OpenAboutWindow_activate
self.builder = builder
def on_OpenAboutWindow_activate(self, menu_item):
self.builder.add_from_file("gui/aboutdialog.ui")
self.aboutdialog = self.builder.get_object("aboutdialog")
self.aboutdialog.run()
self.aboutdialog.destroy()
#modules/mainwindow.py
class MainWindow:
def __init__(self, builder, dic, controller):
self.control = controller
# get gui xml and/or signals
dic["on_file_new_activate"] = self.control.newFile
dic["on_file_open_activate"] = self.control.openFile
dic["on_file_save_activate"] = self.control.saveFile
dic["on_file_close_activate"] = self.control.closeFile
...
# get needed gui objects
self.mainWindow = builder.get_object("mainWindow")
...
Edit: alternative to auto attach signals to callbacks:
Untested code
def start_element(name, attrs):
if name == "signal":
if attrs["handler"]:
handler = attrs["handler"]
#Insert code to verify if handler is part of the collection
#we want.
self.handlerList.append(handler)
def extractSignals(uiFile)
import xml.parsers.expat
p = xml.parsers.expat.ParserCreate()
p.StartElementHandler = self.start_element
p.ParseFile(uiFile)
self.handlerList = []
extractSignals(uiFile)
for handler in handlerList:
dic[handler] = eval(''. join(["self.", handler, "_cb"]))
builder.connect_signals
({
"on_window_destroy" : gtk.main_quit,
"on_buttonQuit_clicked" : gtk.main_quit
})
The problem is basically this, in python's gobject and gtk bindings. Assume we have a class that binds to a signal when constructed:
class ClipboardMonitor (object):
def __init__(self):
clip = gtk.clipboard_get(gtk.gdk.SELECTION_CLIPBOARD)
clip.connect("owner-change", self._clipboard_changed)
The problem is now that, no instance of ClipboardMonitor will ever die. The gtk clipboard is an application-wide object, and connecting to it keeps a reference to the object, since we use the callback self._clipboard_changed.
I'm debating how to work around this using weak references (weakref module), but I have yet to come up with a plan. Anyone have an idea how to pass a callback to the signal registration, and have it behave like a weak reference (if the signal callback is called when the ClipboardMonitor instance is out of scope, it should be a no-op).
Addition: Phrased independently of GObject or GTK+:
How do you provide a callback method to an opaque object, with weakref semantics? If the connecting object goes out of scope, it should be deleted and the callback should act as a no-op; the connectee should not hold a reference to the connector.
To clarify: I explicitly want to avoid having to call a "destructor/finalizer" method
The standard way is to disconnect the signal. This however needs to have a destructor-like method in your class, called explicitly by code which maintains your object. This is necessary, because otherwise you'll get circular dependency.
class ClipboardMonitor(object):
[...]
def __init__(self):
self.clip = gtk.clipboard_get(gtk.gdk.SELECTION_CLIPBOARD)
self.signal_id = self.clip.connect("owner-change", self._clipboard_changed)
def close(self):
self.clip.disconnect(self.signal_id)
As you pointed out, you need weakrefs if you want to avoid explicite destroying. I would write a weak callback factory, like:
import weakref
class CallbackWrapper(object):
def __init__(self, sender, callback):
self.weak_obj = weakref.ref(callback.im_self)
self.weak_fun = weakref.ref(callback.im_func)
self.sender = sender
self.handle = None
def __call__(self, *things):
obj = self.weak_obj()
fun = self.weak_fun()
if obj is not None and fun is not None:
return fun(obj, *things)
elif self.handle is not None:
self.sender.disconnect(self.handle)
self.handle = None
self.sender = None
def weak_connect(sender, signal, callback):
wrapper = CallbackWrapper(sender, callback)
wrapper.handle = sender.connect(signal, wrapper)
return wrapper
(this is a proof of concept code, works for me -- you should probably adapt this piece to your needs). Few notes:
I am storing callback object and function separatelly. You cannot simply make a weakref of a bound method, because bound methods are very temporary objects. Actually weakref.ref(obj.method) will destroy the bound method object instantly after creating a weakref. I didn't check whether it is needed to store a weakref to the function too... I guess if your code is static, you probably can avoid that.
The object wrapper will remove itself from the signal sender when it notices that the weak reference ceased to exist. This is also necessary to destroy the circular dependence between the CallbackWrapper and the signal sender object.
(This answer tracks my progress)
This second version will disconnect as well; I have a convenience function for gobjects, but I actually need this class for a more general case -- both for D-Bus signal callbacks and GObject callbacks.
Anyway, what can one call the WeakCallback implementation style? It is a very clean encapsulation of the weak callback, but with the gobject/dbus specialization unnoticeably tacked on. Beats writing two subclasses for those two cases.
import weakref
class WeakCallback (object):
"""A Weak Callback object that will keep a reference to
the connecting object with weakref semantics.
This allows to connect to gobject signals without it keeping
the connecting object alive forever.
Will use #gobject_token or #dbus_token if set as follows:
sender.disconnect(gobject_token)
dbus_token.remove()
"""
def __init__(self, obj, attr):
"""Create a new Weak Callback calling the method #obj.#attr"""
self.wref = weakref.ref(obj)
self.callback_attr = attr
self.gobject_token = None
self.dbus_token = None
def __call__(self, *args, **kwargs):
obj = self.wref()
if obj:
attr = getattr(obj, self.callback_attr)
attr(*args, **kwargs)
elif self.gobject_token:
sender = args[0]
sender.disconnect(self.gobject_token)
self.gobject_token = None
elif self.dbus_token:
self.dbus_token.remove()
self.dbus_token = None
def gobject_connect_weakly(sender, signal, connector, attr, *user_args):
"""Connect weakly to GObject #sender's #signal,
with a callback in #connector named #attr.
"""
wc = WeakCallback(connector, attr)
wc.gobject_token = sender.connect(signal, wc, *user_args)
not actually tried it yet, but:
class WeakCallback(object):
"""
Used to wrap bound methods without keeping a ref to the underlying object.
You can also pass in user_data and user_kwargs in the same way as with
rpartial. Note that refs will be kept to everything you pass in other than
the callback, which will have a weakref kept to it.
"""
def __init__(self, callback, *user_data, **user_kwargs):
self.im_self = weakref.proxy(callback.im_self, self._invalidated)
self.im_func = weakref.proxy(callback.im_func)
self.user_data = user_data
self.user_kwargs = user_kwargs
def __call__(self, *args, **kwargs):
kwargs.update(self.user_kwargs)
args += self.user_data
self.im_func(self.im_self, *args, **kwargs)
def _invalidated(self, im_self):
"""Called by the weakref.proxy object."""
cb = getattr(self, 'cancel_callback', None)
if cb is not None:
cb()
def add_cancel_function(self, cancel_callback):
"""
A ref will be kept to cancel_callback. It will be called back without
any args when the underlying object dies.
You can wrap it in WeakCallback if you want, but that's a bit too
self-referrential for me to do by default. Also, that would stop you
being able to use a lambda as the cancel_callback.
"""
self.cancel_callback = cancel_callback
def weak_connect(sender, signal, callback):
"""
API-compatible with the function described in
http://stackoverflow.com/questions/1364923/. Mostly used as an example.
"""
cb = WeakCallback(callback)
handle = sender.connect(signal, cb)
cb.add_cancel_function(WeakCallback(sender.disconnect, handle))