wxPython wrapper for deleted object

Hello,

here is the code of an example that causes wxPython to crash. The GridCellAttr instances which I create get cleaned up somewhere, although their Python wrapper still exists.
And I don't know how I can prevent them from being cleaned up.

The programm starts to paint the grid, and the print statements in GetAttr() print "wxPython wrapper for DELETED wxGridCellAttr object! (The C++ object no longer exists.)"

I saw the entry "Prevent access to dead windows" in Robin's todo-list
http://alldunn.com/wxPython/todo/notes224.html
I hope that my code is useful to help finding a solution for this issue.

import wx
import wx.grid

class Report:
  
  def __init__(self):
    self.rows = []
    self.rows.append((1,"Luc","Eupen"))
    self.rows.append((2,"Lennart","Tallinn"))
    self.rows.append((3,"Bill","Redmont"))

    self.columns = []
    self.columns.append("Id")
    self.columns.append("Name")
    self.columns.append("Town")
    
  def getLabel(self):
    return "Famous people"

class MyDataTable(wx.grid.PyGridTableBase):

  def __init__(self, report):
    wx.grid.PyGridTableBase.__init__(self)
    self.report = report
    self.columns = report.columns
    self.rows = report.rows
    self.cellattrs = []
    for col in self.columns:
      cellattr = wx.grid.GridCellAttr()
      cellattr.SetEditor(wx.grid.GridCellTextEditor())
      cellattr.SetRenderer(
        wx.grid.GridCellStringRenderer())
      self.cellattrs.append(cellattr)

  def GetNumberRows(self): return len(self.rows) + 1
  def GetNumberCols(self): return len(self.columns)
    
  def GetValue(self, rowIndex, colIndex):
    try:
      return self.rows[rowIndex][colIndex]
    except IndexError:
      return None

  def GetAttr(self,r,c,x):
    "overridden to handle attributes directly in the table"
    print "GetAttr(%d,%d,%d) -> %s" %
      (r, c, x, repr(self.cellattrs[c]))
    return self.cellattrs[c]
    
  def GetColLabelValue(self, col):
    "Called when the grid needs to display labels"
    return self.columns[col]

class RptGrid(wx.grid.Grid):
  def __init__(self, parent, report):
    wx.grid.Grid.__init__(self, parent, -1)
    table = MyDataTable(report)
    self.SetTable(table,True)
    
    self.SetRowLabelSize(0)
    self.SetMargins(0,0)
    #self.AutoSizeColumns(True)
    
class RptFrame(wx.Frame):
  def __init__(self, parent, id, report):
    title = report.getLabel()
    wx.Frame.__init__(self, parent, id, title)
       self.grid = RptGrid(self,report)
  
class MyApp(wx.App):

  def __init__(self,report):
    self.report = report
    wx.App.__init__(self,0)
    
  def OnInit(self):
    wx.InitAllImageHandlers()
    frame = RptFrame(None,-1,self.report)
    frame.Show()
    self.SetTopWindow(frame)
    return True

if __name__ == '__main__':

  report = Report()
  app = MyApp(report)
  app.MainLoop()
  
best regards
Luc Saffre

···

--
In theory, there is no difference between theory and practice.
In practice, there is.

One solution is to use wx.grid.Grid.SetColAttr(colIndex,cellAttr) instead of storing them myself and overriding wx.grid.GridTableBase.GetAttr()
LS

···

On 28/10/2003 14:57, Luc Saffre wrote:

And I don't know how I can prevent them from being cleaned up.

Luc Saffre wrote:

Hello,

here is the code of an example that causes wxPython to crash. The GridCellAttr instances which I create get cleaned up somewhere, although their Python wrapper still exists.
And I don't know how I can prevent them from being cleaned up.

The programm starts to paint the grid, and the print statements in GetAttr() print "wxPython wrapper for DELETED wxGridCellAttr object! (The C++ object no longer exists.)"

I saw the entry "Prevent access to dead windows" in Robin's todo-list
http://alldunn.com/wxPython/todo/notes224.html

Actually that has been done since 2.3.something, I need to update the script that generates that page as it is incorrectly stating that it is incomplete. (I also need to do some serious updating of the ToDo list...)

Anyway, the message you show above is the result of that fix, otherwise you would have had a hard crash.

The wxGridCell[Attr|Renderer|Editor] classes use an internal reference counting scheme that makes it awkward sometimes from Python. Perhaps I shoudl fiddle with it in 2.5 to see if I can tie it to the Python object's refcount... Changing your GetAttr to be like this takes care of it:

     def GetAttr(self,r,c,x):
         "overridden to handle attributes directly in the table"
         attr = self.cellattrs[c]
         attr.IncRef()
         print "GetAttr(%d,%d,%d) -> %s" % (r, c, x, repr(attr))
         return attr

···

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!

Robin,

thank you for your answer.

Note that the example DOES make a hard crash, despite your fix. And the messages "wrapper for deleted object" come only because I inserted a print statement. It does NOT raise the ValueError( """Attempt to access attribute of a deleted wxPython object"""). So it seems to me that your bugfix does not work in all cases.

Excerpt from your notes: "Making it an easy-to-trigger functionality (sep. wxPython install?) would be hard I suppose, but might be worth it."
--> I agree that it might be worth it. wxPython shouldn't be fool-proof if the price for this is performance. But hard crashes are quite deterring for potential new users.

Thanks also for your workaround using IncRef().

Luc

···

On 28/10/2003 21:06, Robin Dunn wrote:

Luc Saffre wrote:

Hello,

here is the code of an example that causes wxPython to crash. The GridCellAttr instances which I create get cleaned up somewhere, although their Python wrapper still exists.
And I don't know how I can prevent them from being cleaned up.

The programm starts to paint the grid, and the print statements in GetAttr() print "wxPython wrapper for DELETED wxGridCellAttr object! (The C++ object no longer exists.)"

I saw the entry "Prevent access to dead windows" in Robin's todo-list
http://alldunn.com/wxPython/todo/notes224.html

Actually that has been done since 2.3.something, I need to update the script that generates that page as it is incorrectly stating that it is incomplete. (I also need to do some serious updating of the ToDo list...)

Anyway, the message you show above is the result of that fix, otherwise you would have had a hard crash.

The wxGridCell[Attr|Renderer|Editor] classes use an internal reference counting scheme that makes it awkward sometimes from Python. Perhaps I shoudl fiddle with it in 2.5 to see if I can tie it to the Python object's refcount... Changing your GetAttr to be like this takes care of it:

    def GetAttr(self,r,c,x):
        "overridden to handle attributes directly in the table"
        attr = self.cellattrs[c]
        attr.IncRef()
        print "GetAttr(%d,%d,%d) -> %s" % (r, c, x, repr(attr))
        return attr

Luc Saffre wrote:

Robin,

thank you for your answer.

Note that the example DOES make a hard crash, despite your fix. And the messages "wrapper for deleted object" come only because I inserted a print statement. It does NOT raise the ValueError( """Attempt to access attribute of a deleted wxPython object"""). So it seems to me that your bugfix does not work in all cases.

Right, you have to try to access an attribute of the item from Python code. If it is still being accessed from C++ then there is nothing I can do in the Python wrappers to catch that. In this case that can happen because of not calling IncRef when needed and the C++ code deletes the attr object too soon.

···

--
Robin Dunn
Software Craftsman
http://wxPython.org Java give you jitters? Relax with wxPython!