tl; dr - In the PySide application, an object whose method throws an exception will survive even when all other links have been deleted. What for? And what if something needs to be done with it?
When I created a simple CRUDish application using the Model-View-Presenter architecture with the PySide GUI, I discovered some interesting things. In my case:
- The interface is divided into several types - i.e. each tab displaying a different aspect of the data may be its own View class
- First, the views are created, and when they are initialized, they create an instance of their own Presenter, maintaining a normal link to it.
- The host gets a link to View the drives, but saves it as a weak link (
weakref.ref ) to avoid rounding - No other strong references to Presenter exist. (Hosts can indirectly link to the
pypubsub message pypubsub , but this also only retains weak listener links and is not a factor in MCVE below.) - Thus, during normal operation, when the view is deleted (for example, when the tab is closed), its Presenter is subsequently deleted, since its reference counter becomes 0
However, the presenter from which the method threw an exception is not removed as expected. The application continues to function because PySide uses magic to throw exceptions. The host in question continues to receive and respond to any events related to it. But when the View is deleted, the presenter throwing exceptions remains in effect until the entire application is closed. 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')
Open and close the internal view, and you will see that both the presentation and the presenter will be deleted as expected. Open the internal view, press the button to trigger an exception on the presenter, then close the internal view. The view will be deleted, but the presenter will not be until the application exits.
Why? Presumably, no matter what it catches all the exceptions on behalf of PySide, it stores a reference to the object that threw it. Why is this needed?
How to continue (besides writing code that never throws exceptions, of course)? I have enough sense not to rely on __del__ for resource management. I understand that I have no right to expect anything, except that the exception is caught, but not really processed, ideally, but it just strikes me as an unnecessary ugliness. How do I approach this?