tl; dr - 在PySide应用程序中,即使已删除所有其他引用,其方法抛出异常的对象仍将保持活动状态。为什么?什么,如果有的话,应该怎么做呢?
在使用带有PySide GUI的Model-View-Presenter架构构建简单CRUDish应用程序的过程中,我发现了一些奇怪的行为。就我而言:
weakref.ref
)以避免循环pypubsub
消息库进行通信,但这也仅存储对侦听器的弱引用,并且不是下面MCVE中的一个因素。)但是,方法抛出异常的Presenter不会按预期删除。应用程序继续运行,因为PySide使用some magic来捕获异常。有问题的演示者继续接收并响应与其绑定的任何View事件。但是当删除视图时,抛出异常的Presenter将保持活动状态,直到整个应用程序关闭。 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_())
打开并关闭内部视图,您将看到视图和演示者都按预期删除。打开内部视图,单击按钮以在演示者上触发异常,然后关闭内部视图。视图将被删除,但演示者不会在应用程序退出之前。
为什么?据推测,代表PySide捕获所有异常的是存储对抛出它的对象的引用。为什么需要这样做?
我应该如何(除了编写永远不会导致例外的代码)?我有足够的理解不依赖__del__
进行资源管理。我知道我没有权利期待一个被抓住但没有真正处理过的异常之后的任何事情理想地发生,但这只会让我感到不必要的丑陋。我应该如何处理这个问题?
答案 0 :(得分:3)
问题是sys.last_tracback
和sys.last_value
。
当以交互方式引发回溯时,这似乎是模拟的内容,最后一个异常及其回溯分别是sys.last_value
和sys.last_traceback
中的存储。
否则
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
将释放记忆。
值得注意的是,最多一个异常和回溯对可以缓存。这意味着,因为你的理智并且不依赖于del
,所以不会造成大量的伤害。
但是如果要回收内存,只需删除这些值即可。