如何查找Python对象何时/为何失去属性?
更新 2013-02-08
我现在明白了为什么我在小的测试代码中无法重现这个问题。在一个小程序中,Python 的垃圾回收器并不是很活跃。我认为问题在于 Python 正在回收一些仅在 GObject 中被引用的对象。我觉得这可能是与这个bug有关的回归问题,或者是一个新的类似bug。
我之所以发现这个问题,是因为我再次遇到了同样的问题,但这次是我自己的类(它的引用仅来自 GObject 对象)——这次整个字典在对象上被清空了。如果我使用这里的代码来监控一个消失的属性,它却没有消失!看起来额外的引用让这些属性得以保留。这听起来像是垃圾回收器的问题。我通过让对象在初始化时将自己添加到一个全局列表中来确认这一点……这样做也解决了现在出现的问题。
原始问题
我在使用 PyGTK 图形界面时遇到了一些奇怪的行为。我有一个对象,它总是丢失大量属性。我正在尝试确定这是我代码中的一个bug,还是 Python 解释器或 PyGTK 的问题。
我没有调用 delattr()
。我尝试通过重写 __delattr__()
方法,并让它总是抛出异常,来检测是否有任何东西在调用我的对象的 __delattr__()
方法。我能够重现导致对象丢失属性的事件,但异常从未被抛出。我不确定还有什么其他方法可以找出导致对象丢失属性的函数调用(如果有的话)。
这个对象在所有时候都工作得很好,直到它突然丢失了我试图访问的属性。
属性丢失在执行一些与丢失属性的对象无关的 GUI 操作后持续发生。我是偶然发现的;可能还有其他操作导致对象丢失属性。
我在访问消失的属性的方法中添加了 print id(self)
。打印出来的 id 在属性消失前后是一样的。
有什么建议可以帮助我追踪这个问题的根源吗?
参考代码如下: (注意:如果我想出一个简化的测试案例来重现这个问题,我会更新这段代码。目前重现这个bug所需的总代码太大,无法在这里发布。)
这是我丢失属性的对象的类。这显然是实际功能代码的简化版本,但我用这个进行调试,问题依然存在。
它是我自定义的 MenuBar
类的子类。请注意,on_file_import__activate()
方法是通过父类中的一个信号连接到菜单项的。
class FluidClassManagerWindowMenu(MenuBar):
menu_items = [("File",("Import",))]
def __init__(self, parent):
# XXX: different name than self.parent to see if it stops disappearing
self._xxx_my_parent = parent
MenuBar.__init__(self, parent)
def __delattr__(self,attr):
# XXX: trying to find the cause for lost attributes
traceback.print_stack()
def on_file_import__activate(self, widget=None, data=None):
# XXX: this is the same before and after the attributes disappear
print id(self)
# XXX: print the list of attributes to see what disappears
print dir(self)
# XXX: this works until the _xxx_my_parent attribute disappears
print self._xxx_my_parent
如果你感兴趣,这里是我 MenuBar 类的完整源代码。它是一个 pygtkhelpers 的 SlaveView
,继承自 GObject
。pygtkhelpers 委托会自动连接到上面的 on_file_import__activate
方法。
class MenuBar(pygtkhelpers.delegates.SlaveView):
def __init__(self, parent):
SlaveView.__init__(self)
self.parent = parent
def create_ui(self):
menu_bar = gtk.MenuBar()
menu_bar.set_pack_direction(gtk.PACK_DIRECTION_LTR)
for menu_name, items in self.menu_items:
menu = gtk.Menu()
submenu = gtk.MenuItem(menu_name)
submenu.set_submenu(menu)
for item_name in items:
if not item_name:
menu.append(gtk.MenuItem())
continue
menuitem = gtk.MenuItem(item_name)
fixed_item_name = item_name.lower().replace(' ','_')
fixed_menu_name = menu_name.lower().replace(' ','_')
attr_name = '%s_%s' % (fixed_menu_name,fixed_item_name)
# set an attribute like self.edit_vial_layout
# so pygtkhelpers can find the widget to connect the signal from
setattr(self,attr_name,menuitem)
menu.append(menuitem)
menu_bar.append(submenu)
self.vbox = gtk.VBox()
self.vbox.pack_start(menu_bar)
self.vbox.show_all()
self.widget.add(self.vbox)
从对象中消失的属性列表:
'_model', '_props', '_toplevel', '_xxx_my_parent', 'file_import', 'parent', 'slaves', 'testtesttest', 'vbox', 'widget'
属性 parent
是最初消失的那个;我尝试在 ManagerWindowMenu.__init__()
中将它的值赋给 _xxx_my_parent
,但它也消失了。我还在 MenuBar.__init__
中添加了一个我从未访问过的新属性,叫 testtesttest
,它也消失了。
3 个回答
真奇怪。顺便说一下,使用 delattr
和 __delattr__
这两个调用并不常见,所以我怀疑你是不是在处理两个不同的对象,可能是你想要一个对象却得到了另一个。另外,这可能和解释器没有关系,如果真有问题,它会在更低的层面崩溃。
我遇到了一个非常相似的问题。我有一个叫做 SearchWorker 的类,它负责在运行时为图形界面(GUI)创建一个小部件。在这个小部件里,有一个按钮,它的“点击”信号连接到了 SearchWorker 的一个函数。每当这个“点击”信号被触发时,SearchWorker 对象中的很多属性就消失了。
我是在另一个类的处理函数中这样创建 SearchWorker 对象的:
worker = SearchWorker()
我猜测,当那个处理函数结束后,和这个工作者相关的对象发生了一些奇怪的事情。于是我把创建 SearchWorker 的方式改成了:
self.worker = SearchWorker()
这样就解决了我的问题。
请记住,在PyGTK中,很多对象都是从GObject这个框架继承来的。可能在GObject的内部,有一些操作正在进行,这导致你丢失了某些属性。