Why does PySide exception handling extend the lifetime of this object?

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') # 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 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?

+6
source share
1 answer

Task sys.last_tracback and sys.last_value .

When a trace occurs interactively, and it looks like it is emulated, the last exception and its trace are repositories in sys.last_value and sys.last_traceback respectively.

Performance

 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 

free up memory.

It is worth noting that no more than one pair of exceptions and traces can be cached. This means that since you are sane and do not rely on del , there will be no serious damage.

But if you want to restore memory, just delete these values.

+3
source

Source: https://habr.com/ru/post/975969/


All Articles